colorize remove and add actions
[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.setSelectionMode(self.MultiSelection)
21         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
22         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
23
24 class SelectDelegate(QStyledItemDelegate):
25     pass
26
27 class NodeNameDelegate(QStyledItemDelegate):
28     def __init__(self, parent):
29         QStyledItemDelegate.__init__(self)
30
31     def paint(self, painter, option, index):
32         data = "%s" % index.data().toString()
33         model = index.model()
34         select_index = model.index(index.row(), 2, index.parent())
35         select_data = select_index.data().toString()
36
37         if select_data not in ("true", "add", "remove"): # default view
38             QStyledItemDelegate.paint(self, painter, option, index)
39             return
40
41         fm = QFontMetrics(option.font)
42         rect = option.rect
43         rect.setWidth(fm.width(QString(data)) + 8)
44         rect.setHeight(rect.height() - 2)
45         rect.setX(rect.x() + 4)
46         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width() 
47
48         path = QPainterPath()
49         path.addRoundedRect(x, y, w, h, 4, 4)
50
51         painter.save()
52         painter.setRenderHint(QPainter.Antialiasing)
53         painter.drawRoundedRect(rect, 4, 4)
54
55         if select_data == "true": # already in the slice
56             painter.fillPath(path, QColor.fromRgb(0, 250, 0))
57             painter.setPen(QColor.fromRgb(0, 0, 0))
58             painter.drawText(option.rect, 0, QString(data))
59
60         elif select_data == "add": # newly added to the slice
61             painter.fillPath(path, QColor.fromRgb(0, 250, 0))
62             painter.setPen(QColor.fromRgb(0, 0, 0))
63             painter.drawText(option.rect, 0, QString(data))
64             painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
65
66         elif select_data == "remove": # removed from the slice
67             painter.fillPath(path, QColor.fromRgb(250, 0, 0))
68             painter.setPen(QColor.fromRgb(0, 0, 0))
69             painter.drawText(option.rect, 0, QString(data))
70             painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
71
72         painter.restore()
73
74 class TreeItem:
75     def __init__(self, data, parent=None):
76         self.parentItem = parent
77         self.itemData = data
78         self.childItems = []
79
80     def clear(self):
81         for child in self.childItems:
82             child.clear()
83             del child
84         del self.childItems
85         self.childItems = []
86
87     def appendChild(self, child):
88         self.childItems.append(child)
89
90     def child(self, row):
91         return self.childItems[row]
92     
93     def childCount(self):
94         return len(self.childItems)
95
96     def childNumber(self):
97         if self.parentItem:
98             return self.parentItem.childItems.index(self)
99         return 0
100
101     def columnCount(self):
102         return len(self.itemData)
103
104     def data(self, column):
105         return self.itemData[column]
106
107     def insertChildren(self, position, count, columns):
108         if position < 0 or position > len(self.childItems):
109             return False
110         
111         for row in range(count):
112             data = self.data(columns)
113             item = TreeItem(data, self)
114             self.childItems.insert(position, item)
115
116         return True
117
118     def insertColumns(self, position, columns):
119         if position < 0 or position > len(self.itemData):
120             return False
121
122         for column in range(columns):
123             self.itemData.insert(position, QVariant())
124         
125         for child in self.childItems:
126             child.insertColumns(position, columns)
127         
128         return True
129
130     def setData(self, column, value):
131         if column < 0 or column >= len(self.itemData):
132             return False
133
134         self.itemData[column] = value
135         return True
136     
137     def parent(self):
138         return self.parentItem
139
140
141
142 class NodeModel(QAbstractItemModel):
143     def __init__(self, parent):
144         QAbstractItemModel.__init__(self, parent)
145         self.__initRoot()
146
147     def clear(self):
148         self.rootItem.clear()
149         self.__initRoot()
150
151     def __initRoot(self):
152         self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Selected")])
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 in (Qt.DisplayRole, Qt.EditRole):
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         childItem = parentItem.child(row)
171         if childItem:
172             return self.createIndex(row, column, childItem)
173         else:
174             return QModelIndex()
175
176     def insertColumns(self, position, columns, parent):
177         self.beginInsertColumns(parent, position, position + columns -1)
178         ret = self.rootItem.insertColumns(position, columns)
179         self.endInsertColumns()
180         return ret
181
182     def insertRows(self, position, rows, parent):
183         parentItem = self.getItem(parent)
184         self.beginInsertRows(parent, position, position + rows -1)
185         ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
186         self.endInsertRows()
187         return ret
188
189     def parent(self, index):
190         if not index.isValid():
191             return QModelIndex()
192
193         childItem = self.getItem(index)
194         parentItem = childItem.parent()
195         if parentItem is self.rootItem:
196             return QModelIndex()
197
198         return self.createIndex(parentItem.childNumber(), 0, parentItem)
199
200     def rowCount(self, parent=QModelIndex()):
201         parentItem = self.getItem(parent)
202         return parentItem.childCount()
203
204     def columnCount(self, parent=None):
205         return self.rootItem.columnCount()
206
207     def data(self, index, role):
208         if not index.isValid():
209             return QVariant()
210
211         if role != Qt.DisplayRole and role != Qt.EditRole:
212             return QVariant()
213
214         item = self.getItem(index)
215         return item.data(index.column())
216
217     def flags(self, index):
218         if not index.isValid():
219             return 0
220         return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
221
222     def setData(self, index, value, role):
223         if role != Qt.EditRole:
224             return False
225
226         item = self.getItem(index)
227         ret = item.setData(index.column(), value)
228         if ret:
229             self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
230         return ret
231
232
233
234 class SliceWidget(QWidget):
235     def __init__(self, parent):
236         QWidget.__init__(self, parent)
237
238         slicename = QLabel ("Slice : %s"%(config.getSlice() or "None"),self)
239         slicename.setScaledContents(False)
240         searchlabel = QLabel ("Search: ", self)
241         searchlabel.setScaledContents(False)
242         searchbox = QLineEdit(self)
243         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
244
245         toplayout = QHBoxLayout()
246         toplayout.addWidget(slicename, 0, Qt.AlignLeft)
247         toplayout.addStretch()
248         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
249         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
250
251         self.nodeView = NodeView(self)
252         self.nodeModel = NodeModel(self)
253         self.filterModel = QSortFilterProxyModel(self) # enable filtering
254         self.filterModel.setSourceModel(self.nodeModel)
255         self.nodeView.setModel(self.filterModel)
256         self.filterModel.setDynamicSortFilter(True)
257 #        self.nodeView.setModel(self.filterModel)
258         self.nodeView.setModel(self.nodeModel)
259
260         self.nodeNameDelegate = NodeNameDelegate(self)
261         self.selectDelegate = SelectDelegate(self)
262
263         refresh = QPushButton("Update Slice Data", self)
264         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
265         submit = QPushButton("Submit", self)
266         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
267
268         bottomlayout = QHBoxLayout()
269         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
270         bottomlayout.addStretch()
271         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
272
273         layout = QVBoxLayout()
274         layout.addLayout(toplayout)
275         layout.addWidget(self.nodeView)
276         layout.addLayout(bottomlayout)
277         self.setLayout(layout)
278         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
279
280         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
281         self.connect(submit, SIGNAL('clicked()'), self.submit)
282         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
283
284         self.updateView()
285
286     def filter(self, filter):
287         print self.filterModel.rowCount(), self.nodeModel.rowCount()
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         self.filterModel.setFilterKeyColumn(1)
345
346
347 class MainScreen(SfaScreen):
348     def __init__(self, parent):
349         SfaScreen.__init__(self, parent)
350
351         slice = SliceWidget(self)
352         self.init(slice, "Main Window", "OneLab Federation GUI")