3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
6 from sfa.util.rspecHelper import RSpec
7 from sface.config import config
8 from sface.sfiprocess import SfiProcess
9 from sface.screens.sfascreen import SfaScreen
13 node_status = { "in": "Already Selected",
14 "out": "Not Selected",
16 "remove": "To be Removed"}
18 tag_status = { "in": "Already Set",
21 "remove": "To be Removed"}
24 if index.parent().parent().isValid():
30 class NodeView(QTreeView):
31 def __init__(self, parent):
32 QTreeView.__init__(self, parent)
34 self.setAnimated(True)
35 self.setItemsExpandable(True)
36 self.setRootIsDecorated(True)
37 self.setAlternatingRowColors(True)
38 # self.setSelectionMode(self.MultiSelection)
39 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
40 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
41 self.setToolTip("Double click on a row to change its status")
43 def mouseDoubleClickEvent(self, event):
44 index = self.currentIndex()
46 status_index = model.index(index.row(), 2, index.parent())
47 status_data = status_index.data().toString()
48 node_index = model.index(index.row(), 1, index.parent())
49 node_data = node_index.data().toString()
51 if itemType(node_index) == "tag":
52 if status_data == tag_status['in']:
53 model.setData(status_index, QString(tag_status['remove']))
54 elif status_data == tag_status['add']:
55 model.setData(status_index, QString(tag_status['out']))
56 elif status_data == tag_status['remove']:
57 model.setData(status_index, QString(tag_status['in']))
58 else: model.setData(status_index, QString(node_status['out']))
61 if status_data == node_status['in']:
62 model.setData(status_index, QString(node_status['remove']))
63 elif status_data == node_status['out']:
64 model.setData(status_index, QString(node_status['add']))
65 elif status_data in (node_status['add'], node_status['remove']):
66 if node_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
67 else: model.setData(status_index, QString(node_status['out']))
69 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
71 def currentChanged(self, current, previous):
72 model = current.model()
73 node_index = model.index(current.row(), 1, current.parent())
74 node_data = node_index.data().toString()
75 self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
79 class NodeNameDelegate(QStyledItemDelegate):
80 def __init__(self, parent):
81 QStyledItemDelegate.__init__(self, parent)
83 def paint(self, painter, option, index):
85 status_index = model.index(index.row(), 2, index.parent())
86 status_data = status_index.data().toString()
88 fm = QFontMetrics(option.font)
91 if itemType(index) == "node":
92 data = "%s" % index.data().toString()
93 #if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
95 # QStyledItemDelegate.paint(self, painter, option, index)
98 rect.setHeight(rect.height() - 2)
99 rect.setWidth(fm.width(QString(data)) + 6)
100 rect.setX(rect.x() + 5)
101 rect.setY(rect.y() - 1)
102 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
104 path = QPainterPath()
105 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
108 painter.setRenderHint(QPainter.Antialiasing)
110 if status_data == node_status['in']: # already in the slice
111 painter.fillPath(path, QColor("cyan"))
112 painter.setPen(QColor.fromRgb(0, 0, 0))
113 painter.drawText(option.rect, 0, QString(data))
115 elif status_data == node_status['add']: # newly added to the slice
116 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
117 painter.setPen(QColor.fromRgb(0, 0, 0))
118 painter.drawText(option.rect, 0, QString(data))
120 elif status_data == node_status['remove']: # removed from the slice
121 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
122 painter.setPen(QColor.fromRgb(0, 0, 0))
123 painter.drawText(option.rect, 0, QString(data))
126 painter.setPen(QColor.fromRgb(0, 0, 0))
127 painter.drawText(option.rect, 0, QString(data))
131 tag = index.data().toStringList()
132 data = "%s: %s" % (tag[0], tag[1])
133 rect.setHeight(rect.height() - 2)
134 rect.setWidth(fm.width(QString(data)) + 6 + indent)
135 rect.setX(rect.x() + 4 + indent)
136 rect.setY(rect.y() - 1)
138 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
140 path = QPainterPath()
141 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
144 painter.setRenderHint(QPainter.Antialiasing)
146 if status_data == tag_status['in']: # already in the slice
147 painter.fillPath(path, QColor("cyan"))
148 painter.setPen(QColor.fromRgb(0, 0, 0))
149 painter.drawText(option.rect, 0, QString(data))
151 elif status_data == tag_status['add']: # newly added to the slice
152 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
153 painter.setPen(QColor.fromRgb(0, 0, 0))
154 painter.drawText(option.rect, 0, QString(data))
156 elif status_data == tag_status['remove']: # removed from the slice
157 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
158 painter.setPen(QColor.fromRgb(0, 0, 0))
159 painter.drawText(option.rect, 0, QString(data))
162 painter.setPen(QColor.fromRgb(0, 0, 0))
163 painter.drawText(option.rect, 0, QString(data))
169 def __init__(self, data, parent=None):
170 self.parentItem = parent
175 for child in self.childItems:
181 def allChildItems(self):
183 for c in self.childItems:
186 for cc in c.childItems:
190 def appendChild(self, child):
191 self.childItems.append(child)
193 def child(self, row):
194 return self.childItems[row]
196 def childCount(self):
197 return len(self.childItems)
199 def childNumber(self):
201 return self.parentItem.childItems.index(self)
204 def columnCount(self):
205 return len(self.itemData)
207 def data(self, column):
208 return self.itemData[column]
210 def insertChildren(self, position, count, columns):
211 if position < 0 or position > len(self.childItems):
214 for row in range(count):
215 data = self.data(columns)
216 item = TreeItem(data, self)
217 self.childItems.insert(position, item)
221 def insertColumns(self, position, columns):
222 if position < 0 or position > len(self.itemData):
225 for column in range(columns):
226 self.itemData.insert(position, QVariant())
228 for child in self.childItems:
229 child.insertColumns(position, columns)
233 def setData(self, column, value):
234 if column < 0 or column >= len(self.itemData):
237 self.itemData[column] = value
241 return self.parentItem
244 class NodeModel(QAbstractItemModel):
245 def __init__(self, parent):
246 QAbstractItemModel.__init__(self, parent)
250 self.rootItem.clear()
253 def __initRoot(self):
254 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
256 def getItem(self, index):
258 item = index.internalPointer()
259 if isinstance(item, TreeItem):
263 def headerData(self, section, orientation, role):
264 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
265 return self.rootItem.data(section)
268 def index(self, row, column, parent):
269 if not self.hasIndex(row, column, parent):
272 parentItem = self.getItem(parent)
273 childItem = parentItem.child(row)
275 return self.createIndex(row, column, childItem)
279 def insertColumns(self, position, columns, parent):
280 self.beginInsertColumns(parent, position, position + columns -1)
281 ret = self.rootItem.insertColumns(position, columns)
282 self.endInsertColumns()
285 def insertRows(self, position, rows, parent):
286 parentItem = self.getItem(parent)
287 self.beginInsertRows(parent, position, position + rows -1)
288 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
292 def parent(self, index):
293 if not index.isValid():
296 childItem = self.getItem(index)
297 parentItem = childItem.parent()
298 if parentItem is self.rootItem:
301 return self.createIndex(parentItem.childNumber(), 0, parentItem)
303 def rowCount(self, parent=QModelIndex()):
304 parentItem = self.getItem(parent)
305 return parentItem.childCount()
307 def columnCount(self, parent=None):
308 return self.rootItem.columnCount()
310 def data(self, index, role):
311 if not index.isValid():
314 if role != Qt.DisplayRole and role != Qt.EditRole:
317 item = self.getItem(index)
318 return item.data(index.column())
320 def nodestatus(self, index):
321 if not index.isValid():
324 item = self.getItem(index)
325 return item.nodestatus(index.column())
327 def flags(self, index):
328 if not index.isValid():
330 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
332 def setData(self, index, value, role):
333 if role != Qt.EditRole:
336 item = self.getItem(index)
337 ret = item.setData(index.column(), value)
339 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
343 class SliceWidget(QWidget):
344 def __init__(self, parent):
345 QWidget.__init__(self, parent)
347 self.network_names = []
348 self.process = SfiProcess(self)
350 self.slicename = QLabel("", self)
351 self.updateSliceName()
352 self.slicename.setScaledContents(False)
353 searchlabel = QLabel ("Search: ", self)
354 searchlabel.setScaledContents(False)
355 searchbox = QLineEdit(self)
356 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
358 toplayout = QHBoxLayout()
359 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
360 toplayout.addStretch()
361 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
362 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
364 self.nodeView = NodeView(self)
365 self.nodeModel = NodeModel(self)
366 self.filterModel = QSortFilterProxyModel(self) # enable filtering
368 self.nodeNameDelegate = NodeNameDelegate(self)
370 refresh = QPushButton("Update Slice Data", self)
371 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
372 submit = QPushButton("Submit", self)
373 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
375 bottomlayout = QHBoxLayout()
376 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
377 bottomlayout.addStretch()
378 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
380 layout = QVBoxLayout()
381 layout.addLayout(toplayout)
382 layout.addWidget(self.nodeView)
383 layout.addLayout(bottomlayout)
384 self.setLayout(layout)
385 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
387 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
388 self.connect(submit, SIGNAL('clicked()'), self.submit)
389 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
390 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
391 self.nodeSelectionChanged)
395 def submitFinished(self):
396 self.setStatus("<font color='green'>Slice data submitted.</font>")
397 QTimer.singleShot(1000, self.refresh)
399 def refreshFinished(self):
400 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
402 self.parent().signalAll("rspecUpdated")
404 def readSliceRSpec(self):
405 rspec_file = config.getSliceRSpecFile()
406 if os.path.exists(rspec_file):
407 xml = open(rspec_file).read()
411 def setStatus(self, msg, timeout=None):
412 self.parent().setStatus(msg, timeout)
414 def checkRunningProcess(self):
415 if self.process.isRunning():
416 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
420 def filter(self, filter_string):
421 # for hierarchical models QSortFilterProxyModel applies the
422 # sort recursively. if the parent doesn't match the criteria
423 # we won't be able to match the children. so we need to match
424 # parent (by matching the network_names)
425 networks = ["^%s$" % n for n in self.network_names]
426 filters = networks + [str(filter_string)]
427 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
430 if self.checkRunningProcess():
433 rspec = self.readSliceRSpec()
436 all_child = self.nodeModel.rootItem.allChildItems()
438 testbed, hostname, status = c.itemData
439 if isinstance(status, QVariant):
440 status = status.toString()
442 if status == node_status['add']:
443 rspec.add_sliver(hostname)
445 elif str(status) == node_status['remove']:
446 rspec.remove_sliver(hostname)
450 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
453 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
454 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
456 self.process.applyRSpec(rspec)
457 self.setStatus("Sending slice data (RSpec). This will take some time...")
461 if not config.getSlice():
462 self.setStatus("<font color='red'>Slice not set yet!</font>")
465 if self.process.isRunning():
466 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
469 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
470 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
472 self.process.getRSpecFromSM()
473 self.setStatus("Updating slice data. This will take some time...")
475 def updateView(self):
476 global already_in_nodes
477 already_in_nodes = []
478 self.network_names = []
479 self.nodeModel.clear()
481 rspec = self.readSliceRSpec()
485 networks = rspec.get_network_list()
486 for network in networks:
487 self.network_names.append(network)
488 data = [QString(network), QString(""), QString("")]
489 #taglist = QStringList()
490 #attrs = rspec.get_default_sliver_attributes(network)
491 #for (name, value) in attrs:
492 # taglist.append(QString("%s/%s" % (name, value)))
493 # taglist.append(QString("in"))
494 #data.append(taglist)
495 networkItem = TreeItem(data, self.nodeModel.rootItem)
496 all_nodes = rspec.get_node_list(network)
497 sliver_nodes = rspec.get_sliver_list(network)
498 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
500 already_in_nodes += sliver_nodes
502 for node in sliver_nodes:
503 data = [QString(""), QString(node), QString(node_status['in'])]
504 nodeItem = TreeItem(data, networkItem)
505 networkItem.appendChild(nodeItem)
507 attrs = rspec.get_sliver_attributes(node, network)
508 for (name, value) in attrs:
509 tagstring = QStringList([name, value])
510 data = [QString(""), tagstring, QString(tag_status['in'])]
511 tagItem = TreeItem(data, nodeItem)
512 nodeItem.appendChild(tagItem)
514 for node in available_nodes:
515 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out']), QString("")], networkItem)
516 networkItem.appendChild(nodeItem)
518 self.nodeModel.rootItem.appendChild(networkItem)
520 self.filterModel.setSourceModel(self.nodeModel)
521 self.filterModel.setFilterKeyColumn(-1)
522 self.filterModel.setDynamicSortFilter(True)
524 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
525 self.nodeView.setModel(self.filterModel)
526 self.nodeView.expandAll()
527 self.nodeView.resizeColumnToContents(1)
529 def updateSliceName(self):
530 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
532 def nodeSelectionChanged(self, hostname):
533 self.parent().nodeSelectionChanged(hostname)
535 class MainScreen(SfaScreen):
536 def __init__(self, parent):
537 SfaScreen.__init__(self, parent)
539 slice = SliceWidget(self)
540 self.init(slice, "Main Window", "OneLab Federation GUI")
542 def rspecUpdated(self):
543 self.mainwin.rspecWindow.updateView()
545 def configurationChanged(self):
546 self.widget.updateSliceName()
547 self.widget.updateView()
548 self.mainwin.rspecWindow.updateView()
550 def nodeSelectionChanged(self, hostname):
551 self.mainwin.nodeSelectionChanged(hostname)