d43bd62d426697170b8e7c62bca8e55fd77c03f0
[sface.git] / sface / screens / mainscreen.py
1
2 import os
3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
5
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
11
12 already_in_nodes = []
13
14 node_status = { "in": "Already Selected",
15                 "out": "Not Selected",
16                 "add": "To be Added",
17                 "remove": "To be Removed"}
18
19 class NodeView(QTreeView):
20     def __init__(self, parent):
21         QTreeView.__init__(self, parent)
22
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")
31
32     def mouseDoubleClickEvent(self, event):
33         index = self.currentIndex()
34         model = index.model()
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()
39
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']))
47
48         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), hostname_index, hostname_index)
49                 
50         
51
52 class NodeNameDelegate(QStyledItemDelegate):
53     def __init__(self, parent):
54         QStyledItemDelegate.__init__(self, parent)
55
56     def paint(self, painter, option, index):
57         model = index.model()
58         data = "%s" % index.data().toString()
59         status_index = model.index(index.row(), 2, index.parent())
60         status_data = status_index.data().toString()
61
62         if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
63             # default view
64             QStyledItemDelegate.paint(self, painter, option, index)
65             return
66
67         fm = QFontMetrics(option.font)
68         rect = option.rect
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() 
73
74         path = QPainterPath()
75         path.addRoundedRect(x, y, w, h, 4, 4)
76
77         painter.save()
78         painter.setRenderHint(QPainter.Antialiasing)
79         painter.drawRoundedRect(rect, 4, 4)
80
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))
85
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))
92
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))
99
100         painter.restore()
101
102 class TreeItem:
103     def __init__(self, data, parent=None):
104         self.parentItem = parent
105         self.itemData = data
106         self.childItems = []
107
108     def clear(self):
109         for child in self.childItems:
110             child.clear()
111             del child
112         del self.childItems
113         self.childItems = []
114
115     def appendChild(self, child):
116         self.childItems.append(child)
117
118     def child(self, row):
119         return self.childItems[row]
120     
121     def childCount(self):
122         return len(self.childItems)
123
124     def childNumber(self):
125         if self.parentItem:
126             return self.parentItem.childItems.index(self)
127         return 0
128
129     def columnCount(self):
130         return len(self.itemData)
131
132     def data(self, column):
133         return self.itemData[column]
134
135     def insertChildren(self, position, count, columns):
136         if position < 0 or position > len(self.childItems):
137             return False
138         
139         for row in range(count):
140             data = self.data(columns)
141             item = TreeItem(data, self)
142             self.childItems.insert(position, item)
143
144         return True
145
146     def insertColumns(self, position, columns):
147         if position < 0 or position > len(self.itemData):
148             return False
149
150         for column in range(columns):
151             self.itemData.insert(position, QVariant())
152         
153         for child in self.childItems:
154             child.insertColumns(position, columns)
155         
156         return True
157
158     def setData(self, column, value):
159         if column < 0 or column >= len(self.itemData):
160             return False
161
162         self.itemData[column] = value
163         return True
164     
165     def parent(self):
166         return self.parentItem
167
168
169
170 class NodeModel(QAbstractItemModel):
171     def __init__(self, parent):
172         QAbstractItemModel.__init__(self, parent)
173         self.__initRoot()
174
175     def clear(self):
176         self.rootItem.clear()
177         self.__initRoot()
178
179     def __initRoot(self):
180         self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
181
182     def getItem(self, index):
183         if index.isValid():
184             item = index.internalPointer()
185             if item: return item
186         return self.rootItem
187
188     def headerData(self, section, orientation, role):
189         if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
190             return self.rootItem.data(section)
191         return QVariant()
192
193     def index(self, row, column, parent):
194         if not self.hasIndex(row, column, parent):
195             return QModelIndex()
196
197         parentItem = self.getItem(parent)
198         childItem = parentItem.child(row)
199         if childItem:
200             return self.createIndex(row, column, childItem)
201         else:
202             return QModelIndex()
203
204     def insertColumns(self, position, columns, parent):
205         self.beginInsertColumns(parent, position, position + columns -1)
206         ret = self.rootItem.insertColumns(position, columns)
207         self.endInsertColumns()
208         return ret
209
210     def insertRows(self, position, rows, parent):
211         parentItem = self.getItem(parent)
212         self.beginInsertRows(parent, position, position + rows -1)
213         ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
214         self.endInsertRows()
215         return ret
216
217     def parent(self, index):
218         if not index.isValid():
219             return QModelIndex()
220
221         childItem = self.getItem(index)
222         parentItem = childItem.parent()
223         if parentItem is self.rootItem:
224             return QModelIndex()
225
226         return self.createIndex(parentItem.childNumber(), 0, parentItem)
227
228     def rowCount(self, parent=QModelIndex()):
229         parentItem = self.getItem(parent)
230         return parentItem.childCount()
231
232     def columnCount(self, parent=None):
233         return self.rootItem.columnCount()
234
235     def data(self, index, role):
236         if not index.isValid():
237             return QVariant()
238
239         if role != Qt.DisplayRole and role != Qt.EditRole:
240             return QVariant()
241
242         item = self.getItem(index)
243         return item.data(index.column())
244
245     def flags(self, index):
246         if not index.isValid():
247             return 0
248         return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
249
250     def setData(self, index, value, role):
251         if role != Qt.EditRole:
252             return False
253
254         item = self.getItem(index)
255         ret = item.setData(index.column(), value)
256         if ret:
257             self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
258         return ret
259
260
261
262 class SliceWidget(QWidget):
263     def __init__(self, parent):
264         QWidget.__init__(self, parent)
265
266         self.network_names = []
267
268         slicename = QLabel ("Slice : %s"%(config.getSlice() or "None"),self)
269         slicename.setScaledContents(False)
270         searchlabel = QLabel ("Search: ", self)
271         searchlabel.setScaledContents(False)
272         searchbox = QLineEdit(self)
273         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
274
275         toplayout = QHBoxLayout()
276         toplayout.addWidget(slicename, 0, Qt.AlignLeft)
277         toplayout.addStretch()
278         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
279         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
280
281         self.nodeView = NodeView(self)
282         self.nodeModel = NodeModel(self)
283         self.filterModel = QSortFilterProxyModel(self) # enable filtering
284
285         self.nodeNameDelegate = NodeNameDelegate(self)
286
287         refresh = QPushButton("Update Slice Data", self)
288         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
289         submit = QPushButton("Submit", self)
290         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
291
292         bottomlayout = QHBoxLayout()
293         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
294         bottomlayout.addStretch()
295         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
296
297         layout = QVBoxLayout()
298         layout.addLayout(toplayout)
299         layout.addWidget(self.nodeView)
300         layout.addLayout(bottomlayout)
301         self.setLayout(layout)
302         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
303
304         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
305         self.connect(submit, SIGNAL('clicked()'), self.submit)
306         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
307
308         self.updateView()
309
310     def filter(self, filter_string):
311         # for hierarchical models QSortFilterProxyModel applies the
312         # sort recursively. if the parent doesn't match the criteria
313         # we won't be able to match the children. so we need to match
314         # parent (by matching the network_names)
315         networks = ["^%s$" % n for n in self.network_names]
316         filters = networks + [str(filter_string)]
317         self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
318
319     def submit(self):
320         self.parent().setStatus("TODO: Submit not implemented yet!", 3000)
321         
322     def readSliceRSpec(self):
323         rspec_file = config.getSliceRSpecFile()
324         if os.path.exists(rspec_file):
325             xml = open(rspec_file).read()
326             return xml
327         return None
328
329     def refresh(self):
330         if not config.getSlice():
331             self.parent().setStatus("<font color='red'>Slice not set yet!</font>", timeout=None)
332             return
333
334         self.process = SfiProcess()
335         outfile = self.process.getRSpecFromSM()
336         self.parent().setStatus("Updating slice data. This may take some time...", timeout=None)
337         
338         self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
339
340     def refreshFinished(self):
341         del self.process
342         self.parent().setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
343         self.updateView()
344
345     def updateView(self):
346         global already_in_nodes
347         already_in_nodes = []
348         self.network_names = []
349         self.nodeModel.clear()
350         
351         rspec_string = self.readSliceRSpec()
352         if not rspec_string:
353             return None
354
355         networks = rspec_get_networks(rspec_string)
356         for network in networks:
357             self.network_names.append(network)
358             networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
359
360             all_nodes = rspec_get_nodes_from_network(rspec_string, network)
361             sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
362             available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
363
364             already_in_nodes += sliver_nodes
365
366             for node in sliver_nodes:
367                 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
368                 networkItem.appendChild(nodeItem)
369
370             for node in available_nodes:
371                 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
372                 networkItem.appendChild(nodeItem)
373
374             self.nodeModel.rootItem.appendChild(networkItem)
375
376         self.filterModel.setSourceModel(self.nodeModel)
377         self.filterModel.setFilterKeyColumn(-1)
378         self.filterModel.setDynamicSortFilter(True)
379
380         self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
381         self.nodeView.setModel(self.filterModel)
382         self.nodeView.expandAll()
383         self.nodeView.resizeColumnToContents(1)
384
385
386 class MainScreen(SfaScreen):
387     def __init__(self, parent):
388         SfaScreen.__init__(self, parent)
389
390         slice = SliceWidget(self)
391         self.init(slice, "Main Window", "OneLab Federation GUI")