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()
39 if status_data == node_status['in']:
40 model.setData(status_index, QString(node_status['remove']))
41 elif status_data == node_status['out']:
42 model.setData(status_index, QString(node_status['add']))
43 elif status_data in (node_status['add'], node_status['remove']):
44 if hostname_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
45 else: model.setData(status_index, QString(node_status['out']))
47 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), hostname_index, hostname_index)
49 def currentChanged(self, current, previous):
50 model = current.model()
51 hostname_index = model.index(current.row(), 1, current.parent())
52 hostname_data = hostname_index.data().toString()
53 self.emit(SIGNAL('hostnameClicked(QString)'), hostname_data)
57 class NodeNameDelegate(QStyledItemDelegate):
58 def __init__(self, parent):
59 QStyledItemDelegate.__init__(self, parent)
61 def paint(self, painter, option, index):
63 data = "%s" % index.data().toString()
64 status_index = model.index(index.row(), 2, index.parent())
65 status_data = status_index.data().toString()
67 if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
69 QStyledItemDelegate.paint(self, painter, option, index)
72 fm = QFontMetrics(option.font)
74 rect.setWidth(fm.width(QString(data)) + 8)
75 rect.setHeight(rect.height() - 2)
76 rect.setX(rect.x() + 4)
77 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
80 path.addRoundedRect(x, y, w, h, 4, 4)
83 painter.setRenderHint(QPainter.Antialiasing)
84 painter.drawRoundedRect(rect, 4, 4)
86 if status_data == node_status['in']: # already in the slice
87 painter.fillPath(path, QColor("cyan"))
88 painter.setPen(QColor.fromRgb(0, 0, 0))
89 painter.drawText(option.rect, 0, QString(data))
91 elif status_data == node_status['add']: # newly added to the slice
92 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
93 painter.setPen(QColor.fromRgb(0, 0, 0))
94 painter.drawText(option.rect, 0, QString(data))
95 painter.drawRect(x + w + 10, y + 3, 10, 10)
96 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
98 elif status_data == node_status['remove']: # removed from the slice
99 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
100 painter.setPen(QColor.fromRgb(0, 0, 0))
101 painter.drawText(option.rect, 0, QString(data))
102 painter.drawRect(x + w + 10, y + 3, 10, 10)
103 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
109 def __init__(self, data, parent=None):
110 self.parentItem = parent
115 for child in self.childItems:
121 def allChildItems(self):
123 for c in self.childItems:
126 for cc in c.childItems:
130 def appendChild(self, child):
131 self.childItems.append(child)
133 def child(self, row):
134 return self.childItems[row]
136 def childCount(self):
137 return len(self.childItems)
139 def childNumber(self):
141 return self.parentItem.childItems.index(self)
144 def columnCount(self):
145 return len(self.itemData)
147 def data(self, column):
148 return self.itemData[column]
150 def insertChildren(self, position, count, columns):
151 if position < 0 or position > len(self.childItems):
154 for row in range(count):
155 data = self.data(columns)
156 item = TreeItem(data, self)
157 self.childItems.insert(position, item)
161 def insertColumns(self, position, columns):
162 if position < 0 or position > len(self.itemData):
165 for column in range(columns):
166 self.itemData.insert(position, QVariant())
168 for child in self.childItems:
169 child.insertColumns(position, columns)
173 def setData(self, column, value):
174 if column < 0 or column >= len(self.itemData):
177 self.itemData[column] = value
181 return self.parentItem
184 class NodeModel(QAbstractItemModel):
185 def __init__(self, parent):
186 QAbstractItemModel.__init__(self, parent)
190 self.rootItem.clear()
193 def __initRoot(self):
194 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status"), QString("Tags")])
196 def getItem(self, index):
198 item = index.internalPointer()
199 if isinstance(item, TreeItem):
203 def headerData(self, section, orientation, role):
204 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
205 return self.rootItem.data(section)
208 def index(self, row, column, parent):
209 if not self.hasIndex(row, column, parent):
212 parentItem = self.getItem(parent)
213 childItem = parentItem.child(row)
215 return self.createIndex(row, column, childItem)
219 def insertColumns(self, position, columns, parent):
220 self.beginInsertColumns(parent, position, position + columns -1)
221 ret = self.rootItem.insertColumns(position, columns)
222 self.endInsertColumns()
225 def insertRows(self, position, rows, parent):
226 parentItem = self.getItem(parent)
227 self.beginInsertRows(parent, position, position + rows -1)
228 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
232 def parent(self, index):
233 if not index.isValid():
236 childItem = self.getItem(index)
237 parentItem = childItem.parent()
238 if parentItem is self.rootItem:
241 return self.createIndex(parentItem.childNumber(), 0, parentItem)
243 def rowCount(self, parent=QModelIndex()):
244 parentItem = self.getItem(parent)
245 return parentItem.childCount()
247 def columnCount(self, parent=None):
248 return self.rootItem.columnCount()
250 def data(self, index, role):
251 if not index.isValid():
254 if role != Qt.DisplayRole and role != Qt.EditRole:
257 item = self.getItem(index)
258 return item.data(index.column())
260 def flags(self, index):
261 if not index.isValid():
263 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
265 def setData(self, index, value, role):
266 if role != Qt.EditRole:
269 item = self.getItem(index)
270 ret = item.setData(index.column(), value)
272 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
276 class SliceWidget(QWidget):
277 def __init__(self, parent):
278 QWidget.__init__(self, parent)
280 self.network_names = []
281 self.process = SfiProcess(self)
283 self.slicename = QLabel("", self)
284 self.updateSliceName()
285 self.slicename.setScaledContents(False)
286 searchlabel = QLabel ("Search: ", self)
287 searchlabel.setScaledContents(False)
288 searchbox = QLineEdit(self)
289 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
291 toplayout = QHBoxLayout()
292 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
293 toplayout.addStretch()
294 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
295 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
297 self.nodeView = NodeView(self)
298 self.nodeModel = NodeModel(self)
299 self.filterModel = QSortFilterProxyModel(self) # enable filtering
301 self.nodeNameDelegate = NodeNameDelegate(self)
303 refresh = QPushButton("Update Slice Data", self)
304 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
305 submit = QPushButton("Submit", self)
306 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
308 bottomlayout = QHBoxLayout()
309 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
310 bottomlayout.addStretch()
311 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
313 layout = QVBoxLayout()
314 layout.addLayout(toplayout)
315 layout.addWidget(self.nodeView)
316 layout.addLayout(bottomlayout)
317 self.setLayout(layout)
318 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
320 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
321 self.connect(submit, SIGNAL('clicked()'), self.submit)
322 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
323 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
324 self.nodeSelectionChanged)
328 def submitFinished(self):
329 self.setStatus("<font color='green'>Slice data submitted.</font>")
330 QTimer.singleShot(1000, self.refresh)
332 def refreshFinished(self):
333 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
335 self.parent().signalAll("rspecUpdated")
337 def readSliceRSpec(self):
338 rspec_file = config.getSliceRSpecFile()
339 if os.path.exists(rspec_file):
340 xml = open(rspec_file).read()
344 def setStatus(self, msg, timeout=None):
345 self.parent().setStatus(msg, timeout)
347 def checkRunningProcess(self):
348 if self.process.isRunning():
349 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
353 def filter(self, filter_string):
354 # for hierarchical models QSortFilterProxyModel applies the
355 # sort recursively. if the parent doesn't match the criteria
356 # we won't be able to match the children. so we need to match
357 # parent (by matching the network_names)
358 networks = ["^%s$" % n for n in self.network_names]
359 filters = networks + [str(filter_string)]
360 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
363 if self.checkRunningProcess():
366 rspec = self.readSliceRSpec()
369 all_child = self.nodeModel.rootItem.allChildItems()
371 testbed, hostname, status = c.itemData
372 if isinstance(status, QVariant):
373 status = status.toString()
375 if status == node_status['add']:
376 rspec.add_sliver(hostname)
378 elif str(status) == node_status['remove']:
379 rspec.remove_sliver(hostname)
383 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
386 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
387 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
389 self.process.applyRSpec(rspec)
390 self.setStatus("Sending slice data (RSpec). This will take some time...")
394 if not config.getSlice():
395 self.setStatus("<font color='red'>Slice not set yet!</font>")
398 if self.process.isRunning():
399 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
402 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
403 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
405 self.process.getRSpecFromSM()
406 self.setStatus("Updating slice data. This will take some time...")
408 def updateView(self):
409 global already_in_nodes
410 already_in_nodes = []
411 self.network_names = []
412 self.nodeModel.clear()
414 rspec = self.readSliceRSpec()
418 networks = rspec.get_network_list()
419 for network in networks:
420 self.network_names.append(network)
422 for (name, value) in rspec.get_default_sliver_attributes(network):
423 attrs += "%s/%s " % (name, value)
424 networkItem = TreeItem([QString(network), QString(""), QString(""), QString(attrs)], self.nodeModel.rootItem)
426 all_nodes = rspec.get_node_list(network)
427 sliver_nodes = rspec.get_sliver_list(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:
434 for (name, value) in rspec.get_sliver_attributes(node, network):
435 attrs += "%s/%s " % (name, value)
436 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in']), QString(attrs)], networkItem)
437 networkItem.appendChild(nodeItem)
439 for node in available_nodes:
440 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out']), QString("")], networkItem)
441 networkItem.appendChild(nodeItem)
443 self.nodeModel.rootItem.appendChild(networkItem)
445 self.filterModel.setSourceModel(self.nodeModel)
446 self.filterModel.setFilterKeyColumn(-1)
447 self.filterModel.setDynamicSortFilter(True)
449 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
450 self.nodeView.setModel(self.filterModel)
451 self.nodeView.expandAll()
452 self.nodeView.resizeColumnToContents(1)
454 def updateSliceName(self):
455 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
457 def nodeSelectionChanged(self, hostname):
458 self.parent().nodeSelectionChanged(hostname)
460 class MainScreen(SfaScreen):
461 def __init__(self, parent):
462 SfaScreen.__init__(self, parent)
464 slice = SliceWidget(self)
465 self.init(slice, "Main Window", "OneLab Federation GUI")
467 def rspecUpdated(self):
468 self.mainwin.rspecWindow.updateView()
470 def configurationChanged(self):
471 self.widget.updateSliceName()
472 self.widget.updateView()
473 self.mainwin.rspecWindow.updateView()
475 def nodeSelectionChanged(self, hostname):
476 self.mainwin.nodeSelectionChanged(hostname)