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 class NodeView(QTreeView):
19 def __init__(self, parent):
20 QTreeView.__init__(self, parent)
22 self.setAnimated(True)
23 self.setItemsExpandable(True)
24 self.setRootIsDecorated(True)
25 self.setAlternatingRowColors(True)
26 # self.setSelectionMode(self.MultiSelection)
27 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
28 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
29 self.setToolTip("Double click on a row to change its status")
31 def mouseDoubleClickEvent(self, event):
32 index = self.currentIndex()
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 tags_index = model.index(index.row(), 3, index.parent())
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, tags_index)
50 def currentChanged(self, current, previous):
51 model = current.model()
52 hostname_index = model.index(current.row(), 1, current.parent())
53 hostname_data = hostname_index.data().toString()
54 self.emit(SIGNAL('hostnameClicked(QString)'), hostname_data)
58 class NodeNameDelegate(QStyledItemDelegate):
59 def __init__(self, parent):
60 QStyledItemDelegate.__init__(self, parent)
62 def paint(self, painter, option, index):
64 data = "%s" % index.data().toString()
65 status_index = model.index(index.row(), 2, index.parent())
66 status_data = status_index.data().toString()
68 if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
70 QStyledItemDelegate.paint(self, painter, option, index)
73 fm = QFontMetrics(option.font)
75 rect.setWidth(fm.width(QString(data)) + 8)
76 rect.setHeight(rect.height() - 2)
77 rect.setX(rect.x() + 4)
78 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
81 path.addRoundedRect(x, y, w, h, 4, 4)
84 painter.setRenderHint(QPainter.Antialiasing)
85 painter.drawRoundedRect(rect, 4, 4)
87 if status_data == node_status['in']: # already in the slice
88 painter.fillPath(path, QColor("cyan"))
89 painter.setPen(QColor.fromRgb(0, 0, 0))
90 painter.drawText(option.rect, 0, QString(data))
92 elif status_data == node_status['add']: # newly added to the slice
93 painter.fillPath(path, QColor.fromRgb(0, 250, 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(0, 250, 0))
99 elif status_data == node_status['remove']: # removed from the slice
100 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
101 painter.setPen(QColor.fromRgb(0, 0, 0))
102 painter.drawText(option.rect, 0, QString(data))
103 painter.drawRect(x + w + 10, y + 3, 10, 10)
104 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
109 class NodeTagDelegate(QStyledItemDelegate):
110 def __init__(self, parent):
111 QStyledItemDelegate.__init__(self, parent)
113 def paint(self, painter, option, index):
114 model = index.model()
115 taglist = index.data().toStringList()
116 status_index = model.index(index.row(), 2, index.parent())
117 status_data = status_index.data().toString()
119 if taglist.isEmpty() or status_data not in (node_status['in'], ""):
123 fm = QFontMetrics(option.font)
125 rect.setHeight(rect.height() - 2)
126 rect.setX(rect.x() + 4)
128 for i in range(0, len(taglist), 2):
129 # This taglist is pretty much of a hack. Not quite sure the best
130 # way to use Qt data structures.
132 status = taglist[i+1]
133 rect.setWidth(fm.width(QString(data)) + 8)
134 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
136 path = QPainterPath()
137 path.addRoundedRect(x, y, w, h, 4, 4)
140 painter.setRenderHint(QPainter.Antialiasing)
141 painter.drawRoundedRect(rect, 4, 4)
143 if status == 'in': # already in the slice
144 painter.fillPath(path, QColor("cyan"))
145 painter.setPen(QColor.fromRgb(0, 0, 0))
146 painter.drawText(option.rect, 0, QString(data))
148 elif status == 'add': # newly added to the slice
149 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
150 painter.setPen(QColor.fromRgb(0, 0, 0))
151 painter.drawText(option.rect, 0, QString(data))
153 elif status == 'remove': # removed from the slice
154 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
155 painter.setPen(QColor.fromRgb(0, 0, 0))
156 painter.drawText(option.rect, 0, QString(data))
159 rect.setX(rect.x() + rect.width() + 4)
163 def __init__(self, data, parent=None):
164 self.parentItem = parent
169 for child in self.childItems:
175 def allChildItems(self):
177 for c in self.childItems:
180 for cc in c.childItems:
184 def appendChild(self, child):
185 self.childItems.append(child)
187 def child(self, row):
188 return self.childItems[row]
190 def childCount(self):
191 return len(self.childItems)
193 def childNumber(self):
195 return self.parentItem.childItems.index(self)
198 def columnCount(self):
199 return len(self.itemData)
201 def data(self, column):
202 return self.itemData[column]
204 def insertChildren(self, position, count, columns):
205 if position < 0 or position > len(self.childItems):
208 for row in range(count):
209 data = self.data(columns)
210 item = TreeItem(data, self)
211 self.childItems.insert(position, item)
215 def insertColumns(self, position, columns):
216 if position < 0 or position > len(self.itemData):
219 for column in range(columns):
220 self.itemData.insert(position, QVariant())
222 for child in self.childItems:
223 child.insertColumns(position, columns)
227 def setData(self, column, value):
228 if column < 0 or column >= len(self.itemData):
231 self.itemData[column] = value
235 return self.parentItem
238 class NodeModel(QAbstractItemModel):
239 def __init__(self, parent):
240 QAbstractItemModel.__init__(self, parent)
244 self.rootItem.clear()
247 def __initRoot(self):
248 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status"), QString("Tags")])
250 def getItem(self, index):
252 item = index.internalPointer()
253 if isinstance(item, TreeItem):
257 def headerData(self, section, orientation, role):
258 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
259 return self.rootItem.data(section)
262 def index(self, row, column, parent):
263 if not self.hasIndex(row, column, parent):
266 parentItem = self.getItem(parent)
267 childItem = parentItem.child(row)
269 return self.createIndex(row, column, childItem)
273 def insertColumns(self, position, columns, parent):
274 self.beginInsertColumns(parent, position, position + columns -1)
275 ret = self.rootItem.insertColumns(position, columns)
276 self.endInsertColumns()
279 def insertRows(self, position, rows, parent):
280 parentItem = self.getItem(parent)
281 self.beginInsertRows(parent, position, position + rows -1)
282 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
286 def parent(self, index):
287 if not index.isValid():
290 childItem = self.getItem(index)
291 parentItem = childItem.parent()
292 if parentItem is self.rootItem:
295 return self.createIndex(parentItem.childNumber(), 0, parentItem)
297 def rowCount(self, parent=QModelIndex()):
298 parentItem = self.getItem(parent)
299 return parentItem.childCount()
301 def columnCount(self, parent=None):
302 return self.rootItem.columnCount()
304 def data(self, index, role):
305 if not index.isValid():
308 if role != Qt.DisplayRole and role != Qt.EditRole:
311 item = self.getItem(index)
312 return item.data(index.column())
314 def flags(self, index):
315 if not index.isValid():
317 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
319 def setData(self, index, value, role):
320 if role != Qt.EditRole:
323 item = self.getItem(index)
324 ret = item.setData(index.column(), value)
326 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
330 class SliceWidget(QWidget):
331 def __init__(self, parent):
332 QWidget.__init__(self, parent)
334 self.network_names = []
335 self.process = SfiProcess(self)
337 self.slicename = QLabel("", self)
338 self.updateSliceName()
339 self.slicename.setScaledContents(False)
340 searchlabel = QLabel ("Search: ", self)
341 searchlabel.setScaledContents(False)
342 searchbox = QLineEdit(self)
343 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
345 toplayout = QHBoxLayout()
346 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
347 toplayout.addStretch()
348 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
349 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
351 self.nodeView = NodeView(self)
352 self.nodeModel = NodeModel(self)
353 self.filterModel = QSortFilterProxyModel(self) # enable filtering
355 self.nodeNameDelegate = NodeNameDelegate(self)
356 self.nodeTagDelegate = NodeTagDelegate(self)
358 refresh = QPushButton("Update Slice Data", self)
359 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
360 submit = QPushButton("Submit", self)
361 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
363 bottomlayout = QHBoxLayout()
364 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
365 bottomlayout.addStretch()
366 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
368 layout = QVBoxLayout()
369 layout.addLayout(toplayout)
370 layout.addWidget(self.nodeView)
371 layout.addLayout(bottomlayout)
372 self.setLayout(layout)
373 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
375 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
376 self.connect(submit, SIGNAL('clicked()'), self.submit)
377 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
378 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
379 self.nodeSelectionChanged)
383 def submitFinished(self):
384 self.setStatus("<font color='green'>Slice data submitted.</font>")
385 QTimer.singleShot(1000, self.refresh)
387 def refreshFinished(self):
388 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
390 self.parent().signalAll("rspecUpdated")
392 def readSliceRSpec(self):
393 rspec_file = config.getSliceRSpecFile()
394 if os.path.exists(rspec_file):
395 xml = open(rspec_file).read()
399 def setStatus(self, msg, timeout=None):
400 self.parent().setStatus(msg, timeout)
402 def checkRunningProcess(self):
403 if self.process.isRunning():
404 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
408 def filter(self, filter_string):
409 # for hierarchical models QSortFilterProxyModel applies the
410 # sort recursively. if the parent doesn't match the criteria
411 # we won't be able to match the children. so we need to match
412 # parent (by matching the network_names)
413 networks = ["^%s$" % n for n in self.network_names]
414 filters = networks + [str(filter_string)]
415 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
418 if self.checkRunningProcess():
421 rspec = self.readSliceRSpec()
424 all_child = self.nodeModel.rootItem.allChildItems()
426 testbed, hostname, status = c.itemData
427 if isinstance(status, QVariant):
428 status = status.toString()
430 if status == node_status['add']:
431 rspec.add_sliver(hostname)
433 elif str(status) == node_status['remove']:
434 rspec.remove_sliver(hostname)
438 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
441 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
442 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
444 self.process.applyRSpec(rspec)
445 self.setStatus("Sending slice data (RSpec). This will take some time...")
449 if not config.getSlice():
450 self.setStatus("<font color='red'>Slice not set yet!</font>")
453 if self.process.isRunning():
454 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
457 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
458 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
460 self.process.getRSpecFromSM()
461 self.setStatus("Updating slice data. This will take some time...")
463 def updateView(self):
464 global already_in_nodes
465 already_in_nodes = []
466 self.network_names = []
467 self.nodeModel.clear()
469 rspec = self.readSliceRSpec()
473 networks = rspec.get_network_list()
474 for network in networks:
475 self.network_names.append(network)
476 data = [QString(network), QString(""), QString("")]
477 taglist = QStringList()
478 attrs = rspec.get_default_sliver_attributes(network)
479 for (name, value) in attrs:
480 taglist.append(QString("%s/%s" % (name, value)))
481 taglist.append(QString("in"))
483 networkItem = TreeItem(data, self.nodeModel.rootItem)
484 all_nodes = rspec.get_node_list(network)
485 sliver_nodes = rspec.get_sliver_list(network)
486 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
488 already_in_nodes += sliver_nodes
490 for node in sliver_nodes:
491 data = [QString(""), QString(node), QString(node_status['in'])]
492 taglist = QStringList()
493 attrs = rspec.get_sliver_attributes(node, network)
494 for (name, value) in attrs:
495 taglist.append(QString("%s/%s" % (name, value)))
496 taglist.append(QString("in"))
498 nodeItem = TreeItem(data, networkItem)
500 attrs = rspec.get_sliver_attributes(node, network)
501 networkItem.appendChild(nodeItem)
503 for node in available_nodes:
504 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out']), QString("")], networkItem)
505 networkItem.appendChild(nodeItem)
507 self.nodeModel.rootItem.appendChild(networkItem)
509 self.filterModel.setSourceModel(self.nodeModel)
510 self.filterModel.setFilterKeyColumn(-1)
511 self.filterModel.setDynamicSortFilter(True)
513 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
514 self.nodeView.setItemDelegateForColumn(3, self.nodeTagDelegate)
515 self.nodeView.setModel(self.filterModel)
516 self.nodeView.expandAll()
517 self.nodeView.resizeColumnToContents(1)
519 def updateSliceName(self):
520 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
522 def nodeSelectionChanged(self, hostname):
523 self.parent().nodeSelectionChanged(hostname)
525 class MainScreen(SfaScreen):
526 def __init__(self, parent):
527 SfaScreen.__init__(self, parent)
529 slice = SliceWidget(self)
530 self.init(slice, "Main Window", "OneLab Federation GUI")
532 def rspecUpdated(self):
533 self.mainwin.rspecWindow.updateView()
535 def configurationChanged(self):
536 self.widget.updateSliceName()
537 self.widget.updateView()
538 self.mainwin.rspecWindow.updateView()
540 def nodeSelectionChanged(self, hostname):
541 self.mainwin.nodeSelectionChanged(hostname)