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