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