ac0dedbe804627cb8ba135fac1010b6b31d2f7ed
[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 class NodeView(QTreeView):
13     def __init__(self, parent):
14         QTreeView.__init__(self, parent)
15
16         self.setAnimated(True)
17         self.setItemsExpandable(True)
18         self.setRootIsDecorated(True)
19         self.setAlternatingRowColors(True)
20         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
21         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
22
23 class SelectDelegate(QStyledItemDelegate):
24     pass
25
26 class NodeNameDelegate(QStyledItemDelegate):
27     def __init__(self, parent):
28         QStyledItemDelegate.__init__(self)
29
30     def paint(self, painter, option, index):
31         data = "%s" % index.data().toString()
32
33 #         model = index.model()
34 #         select_index = model.index(index.row(), 2, index.parent())
35 #         select_data = select_index.data().toString()
36 #         if select_data == "false":
37 #             print select_data
38 #             model.setData(index, QString("*%s" % data), Qt.EditRole)
39 #             model.setData(select_index, QString("true"), Qt.EditRole)
40
41
42         if data.startswith("*"): # already in the sliver
43             data = " %s " % data[1:]
44
45             fm = QFontMetrics(option.font)
46             rect = option.rect
47             rect.setWidth(fm.width(QString(data)))
48             rect.setHeight(rect.height() - 2)
49             rect.setX(rect.x() + 1)
50             x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
51
52             path = QPainterPath()
53             path.addRoundedRect(x, y, w, h, 4, 4)
54
55             painter.save()
56             painter.setRenderHint(QPainter.Antialiasing)
57             painter.drawRoundedRect(rect, 4, 4)
58             painter.fillPath(path, QColor.fromRgb(0, 250, 0))
59             painter.setPen(QColor.fromRgb(0, 0, 0))
60             painter.drawText(option.rect, 0, QString(data))
61             painter.restore()
62         else: # others, fall back to default view
63             QStyledItemDelegate.paint(self, painter, option, index)
64
65 class TreeItem:
66     def __init__(self, data, parent=None):
67         self.parentItem = parent
68         self.itemData = data
69         self.childItems = []
70
71     def clear(self):
72         for child in self.childItems:
73             child.clear()
74             del child
75         del self.childItems
76         self.childItems = []
77
78     def appendChild(self, child):
79         self.childItems.append(child)
80
81     def child(self, row):
82         return self.childItems[row]
83     
84     def childCount(self):
85         return len(self.childItems)
86
87     def childNumber(self):
88         if self.parentItem:
89             return self.parentItem.childItems.index(self)
90         return 0
91
92     def columnCount(self):
93         return len(self.itemData)
94
95     def data(self, column):
96         return self.itemData[column]
97
98     def insertChildren(self, position, count, columns):
99         if position < 0 or position > len(self.childItems):
100             return False
101         
102         for row in range(count):
103             data = self.data(columns)
104             item = TreeItem(data, self)
105             self.childItems.insert(position, item)
106
107         return True
108
109     def insertColumns(self, position, columns):
110         if position < 0 or position > len(self.itemData):
111             return False
112
113         for column in range(columns):
114             self.itemData.insert(position, QVariant())
115         
116         for child in self.childItems:
117             child.insertColumns(position, columns)
118         
119         return True
120
121     def setData(self, column, value):
122         if column < 0 or column >= len(self.itemData):
123             return False
124
125         self.itemData[column] = value
126         return True
127
128     def row(self):
129         if (self.parentItem):
130             try:
131                 return self.parentItem.childItems.index(self)
132             except ValueError:
133                 return 0
134         return 0
135     
136     def parent(self):
137         return self.parentItem
138
139
140
141 class NodeModel(QAbstractItemModel):
142     def __init__(self, parent):
143         QAbstractItemModel.__init__(self, parent)
144         self.__initRoot()
145
146     def clear(self):
147         self.rootItem.clear()
148         self.__initRoot()
149
150     def __initRoot(self):
151         self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Selected")])
152
153
154     def getItem(self, index):
155         if index.isValid():
156             item = index.internalPointer()
157             if item: return item
158         return self.rootItem
159
160     def headerData(self, section, orientation, role):
161         if orientation == Qt.Horizontal and role == Qt.DisplayRole:
162             return self.rootItem.data(section)
163         return QVariant()
164
165     def index(self, row, column, parent):
166         if not self.hasIndex(row, column, parent):
167             return QModelIndex()
168
169         parentItem = self.getItem(parent)
170             
171         childItem = parentItem.child(row)
172         if childItem:
173             return self.createIndex(row, column, childItem)
174         else:
175             return QModelIndex()
176
177     def insertColumns(self, position, columns, parent):
178         self.beginInsertColumns(parent, position, position + columns -1)
179         ret = self.rootItem.insertColumns(position, columns)
180         self.endInsertColumns()
181         return ret
182
183     def insertRows(self, position, rows, parent):
184         parentItem = self.getItem(parent)
185         self.beginInsertRows(parent, position, position + rows -1)
186         ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
187         self.endInsertRows()
188         return ret
189
190     def parent(self, index):
191         if not index.isValid():
192             return QModelIndex()
193
194         childItem = self.getItem(index)
195         parentItem = childItem.parent()
196
197         if parentItem is self.rootItem:
198             return QModelIndex()
199
200         return self.createIndex(parentItem.row(), 0, parentItem)
201
202     def rowCount(self, parent=QModelIndex()):
203         parentItem = self.getItem(parent)
204         return parentItem.childCount()
205
206     def columnCount(self, parent=None):
207         return self.rootItem.columnCount()
208
209     def data(self, index, role):
210         if not index.isValid():
211             return QVariant()
212
213         if role != Qt.DisplayRole and role != Qt.EditRole:
214             return QVariant()
215
216         item = self.getItem(index)
217         return item.data(index.column())
218
219     def flags(self, index):
220         if not index.isValid():
221             return 0
222         return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
223
224     def setData(self, index, value, role):
225         if role != Qt.EditRole:
226             return False
227
228         item = self.getItem(index)
229         ret = item.setData(index.column(), value)
230         if ret:
231             self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
232         return ret
233
234
235
236 class SliceWidget(QWidget):
237     def __init__(self, parent):
238         QWidget.__init__(self, parent)
239
240         slicename = QLabel ("Slice : %s"%(config.getSlice() or "None"),self)
241         slicename.setScaledContents(False)
242         searchlabel = QLabel ("Search: ", self)
243         searchlabel.setScaledContents(False)
244         searchbox = QLineEdit(self)
245         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
246
247         toplayout = QHBoxLayout()
248         toplayout.addWidget(slicename, 0, Qt.AlignLeft)
249         toplayout.addStretch()
250         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
251         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
252
253         self.nodeView = NodeView(self)
254         self.nodeModel = NodeModel(self)
255         self.filterModel = QSortFilterProxyModel(self) # enable filtering
256         self.filterModel.setSourceModel(self.nodeModel)
257         self.nodeView.setModel(self.filterModel)
258         self.filterModel.setDynamicSortFilter(True)
259         self.nodeView.setModel(self.nodeModel)
260
261         self.nodeNameDelegate = NodeNameDelegate(self)
262         self.selectDelegate = SelectDelegate(self)
263
264         refresh = QPushButton("Update Slice Data", self)
265         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
266         submit = QPushButton("Submit", self)
267         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
268
269         bottomlayout = QHBoxLayout()
270         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
271         bottomlayout.addStretch()
272         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
273
274         layout = QVBoxLayout()
275         layout.addLayout(toplayout)
276         layout.addWidget(self.nodeView)
277         layout.addLayout(bottomlayout)
278         self.setLayout(layout)
279         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
280
281         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
282         self.connect(submit, SIGNAL('clicked()'), self.submit)
283         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
284
285         self.updateView()
286
287     def filter(self, filter):
288         self.filterModel.setFilterRegExp(QRegExp(filter))
289
290     def submit(self):
291         self.parent().setStatus("TODO: Submit not implemented yet!", 3000)
292         
293     def readSliceRSpec(self):
294         rspec_file = config.getSliceRSpecFile()
295         if os.path.exists(rspec_file):
296             xml = open(rspec_file).read()
297             return xml
298         return None
299
300     def refresh(self):
301         if not config.getSlice():
302             self.parent().setStatus("<font color='red'>Slice not set yet!</font>", timeout=None)
303             return
304
305         self.process = SfiProcess()
306         outfile = self.process.getRSpecFromSM()
307         self.parent().setStatus("Updating slice data. This may take some time...", timeout=None)
308         
309         self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
310
311     def refreshFinished(self):
312         del self.process
313         self.parent().setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
314         self.updateView()
315
316     def updateView(self):
317         self.nodeModel.clear()
318         rspec_string = self.readSliceRSpec()
319         if not rspec_string:
320             return None
321
322         networks = rspec_get_networks(rspec_string)
323         for network in networks:
324             networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
325
326             all_nodes = rspec_get_nodes_from_network(rspec_string, network)
327             sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
328             available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
329
330             for node in sliver_nodes:
331                 nodeItem = TreeItem([QString(""), QString("*%s" % node), QString("true")], networkItem)
332                 networkItem.appendChild(nodeItem)
333
334             for node in available_nodes:
335                 nodeItem = TreeItem([QString(""), QString(node), QString("false")], networkItem)
336                 networkItem.appendChild(nodeItem)
337
338             self.nodeModel.rootItem.appendChild(networkItem)
339
340         self.nodeView.expandAll()
341         self.nodeView.resizeColumnToContents(1)
342         self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
343         self.nodeView.setItemDelegateForColumn(2, self.selectDelegate)
344
345
346 class MainScreen(SfaScreen):
347     def __init__(self, parent):
348         SfaScreen.__init__(self, parent)
349
350         slice = SliceWidget(self)
351         self.init(slice, "Main Window", "OneLab Federation GUI")