handle blank rspecs without faulting
[sface.git] / sface / sliceview.py
1 import datetime
2 import os
3 import pickle
4 from PyQt4.QtCore import *
5 from PyQt4.QtGui import *
6
7 from sfa.storage.record import SfaRecord, SliceRecord, AuthorityRecord
8 from sface.config import config
9 from sface.sfidata import SfiData
10 from sface.sfiprocess import SfiProcess
11
12 NAME_COLUMN = 0
13 MEMBERSHIP_STATUS_COLUMN = 1
14
15 # maximum length of a name to display before clipping
16 NAME_MAX_LEN = 48
17
18 MODE_AUTHORITY_SLICES = 0x01
19 MODE_USER_SLICES =      0x02
20 MODE_SELECT_SINGLE =    0x10
21
22 slice_status = { "in": "Selected",
23                 "out": "Not Selected"}
24
25 color_status = { "in": QColor.fromRgb(0, 250, 250), }
26
27 class SliceNameDelegate(QStyledItemDelegate):
28     def __init__(self, parent):
29         QStyledItemDelegate.__init__(self, parent)
30
31     def displayText(self, value, locale):
32         data = str(QStyledItemDelegate.displayText(self, value, locale))
33         if (len(data)>NAME_MAX_LEN):
34             data = data[:(NAME_MAX_LEN-3)] + "..."
35         return QString(data)
36
37     def paint(self, painter, option, index):
38         model = index.model()
39         data = str(self.displayText(index.data(), QLocale()))
40         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
41         status_data = status_index.data().toString()
42
43         fm = QFontMetrics(option.font)
44         rect = QRect(option.rect)
45
46         rect.setHeight(rect.height() - 2)
47         rect.setWidth(fm.width(QString(data)) + 6)
48         rect.setX(rect.x() + 5)
49         rect.setY(rect.y() - 1)
50
51         textRect = QRect(option.rect)
52         textRect.setWidth(fm.width(QString(data)) + 6)
53         textRect.setX(rect.x())
54
55         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
56
57         path = QPainterPath()
58         path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
59
60         painter.save()
61         painter.setRenderHint(QPainter.Antialiasing)
62
63         if option.state & QStyle.State_Selected:
64             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
65
66         color = None
67         for x in slice_status.keys():
68             if (slice_status[x] == status_data) and (x in color_status):
69                 color = color_status[x]
70
71         if color != None:
72             painter.fillPath(path, color)
73         painter.setPen(QColor.fromRgb(0, 0, 0))
74         painter.drawText(textRect, Qt.AlignVCenter, QString(data))
75
76         painter.restore()
77
78 class SliceView(QTableView):
79     def __init__(self, mode=0, parent=None):
80         QTableView.__init__(self, parent)
81         self.mode = mode
82
83         self.setSelectionBehavior(QAbstractItemView.SelectRows)
84         self.setSelectionMode(QAbstractItemView.SingleSelection)
85         self.setAlternatingRowColors(True)
86         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
87         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
88         self.setToolTip("Double click on a row to change its status.")
89
90         self.setItemDelegateForColumn(0, SliceNameDelegate(self))
91
92     def keyPressEvent(self, event):
93         if (event.key() == Qt.Key_Space):
94             self.toggleSelection()
95         else:
96             QTableView.keyPressEvent(self, event)
97
98     def selectionChanged(self, selected, deselected):
99         QTableView.selectionChanged(self, selected, deselected)
100         if (self.mode & MODE_SELECT_SINGLE) != 0:
101             self.unselectAll()
102             self.toggleSelection()
103
104     def mouseDoubleClickEvent(self, event):
105         self.toggleSelection()
106
107     def unselectAll(self):
108         index = self.currentIndex()
109         model = index.model()
110         for row in range(0, model.rowCount()):
111             status_index = model.index(row, MEMBERSHIP_STATUS_COLUMN, index.parent())
112             model.setData(status_index, QString(slice_status['out']))
113
114         #model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"),
115         #            model.index(0, MEMBERSHIP_STATUS_COLUMN, index.parent()),
116         #            model.index(model.rowCount(), MEMBERSHIP_STATUS_COLUMN, index.parent()))
117
118     def toggleSelection(self):
119         index = self.currentIndex()
120         model = index.model()
121         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
122         status_data = status_index.data().toString()
123         node_index = model.index(index.row(), NAME_COLUMN, index.parent())
124         node_data = node_index.data().toString()
125
126         if status_data == slice_status['in']:
127             model.setData(status_index, QString(slice_status['out']))
128         else:
129             model.setData(status_index, QString(slice_status['in']))
130
131         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
132
133     def currentChanged(self, current, previous):
134         model = current.model()
135         node_index = model.index(current.row(), 0, current.parent())
136         node_data = node_index.data().toString()
137
138 class SliceModel(QStandardItemModel):
139     def __init__(self, rows=0, columns=4, parent=None, mode=MODE_AUTHORITY_SLICES):
140          QStandardItemModel.__init__(self, rows, columns, parent)
141          self.mode = mode
142
143     def updateModel(self):
144         self.clear()
145
146         if (self.mode & MODE_AUTHORITY_SLICES) != 0:
147             slice_names = SfiData().getAuthorityHrns(type="slice")
148         else: # MODE_USER_SLICES
149             slice_names = SfiData().getUserSliceHrns()
150
151         rootItem = self.invisibleRootItem()
152
153         for name in slice_names:
154             rootItem.appendRow([self.readOnlyItem(name),
155                                 self.readOnlyItem(slice_status["out"])])
156
157         headers = QStringList() << "Slice Name" << "Status"
158         self.setHorizontalHeaderLabels(headers)
159
160     def readOnlyItem(self, x):
161         item = QStandardItem(QString(x))
162         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
163         return item
164
165     def getSelectedSlices(self):
166         slices = []
167         item = self.invisibleRootItem()
168         children = item.rowCount()
169         for row in range(0, children):
170             childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
171             childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
172
173             if (childStatus == slice_status['in']):
174                 slices.append(childName)
175
176         return slices
177
178 class SlicePickerWindow(QDialog):
179     def __init__(self, mode=MODE_AUTHORITY_SLICES, parent=None):
180         super(SlicePickerWindow, self).__init__(parent)
181         self.mode = mode
182         self.slices = None
183
184         self.setWindowTitle("Slice Picker")
185
186         sliceLabel = QLabel("Slices:")
187         self.sliceView = SliceView(mode=self.mode)
188
189         self.status = QLabel("")
190         self.status.setMaximumWidth(640)
191
192         self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
193         self.buttonBox.button(QDialogButtonBox.Ok).setDefault(True)
194
195         layout = QVBoxLayout()
196         layout.addWidget(sliceLabel)
197         layout.addWidget(self.sliceView)
198         layout.addWidget(self.status)
199         layout.addWidget(self.buttonBox)
200         self.setLayout(layout)
201
202         self.connect(self.buttonBox, SIGNAL("accepted()"), self, SLOT("accept()"))
203         self.connect(self.buttonBox, SIGNAL("rejected()"), self, SLOT("reject()"))
204
205         self.sliceModel = SliceModel(mode=mode)
206
207         self.refresh()
208
209     def accept(self):
210         self.slices = self.sliceModel.getSelectedSlices()
211         print self.slices
212         QDialog.accept(self)
213
214     def setStatus(self, x):
215         self.status.setText(x)
216
217     def refresh(self):
218         if (self.mode & MODE_AUTHORITY_SLICES) != 0:
219             self.refreshAuthority()
220         else:
221             self.refreshUser()
222
223     def refreshUser(self):
224         self.process = SfiProcess(self)
225         self.connect(self.process, SIGNAL('finished()'), self.getUserRecordFinished)
226
227         self.process.getRecord(hrn=config.getUser(), filename=config.getUserRecordFile())
228         self.setStatus("Refreshing user record. This will take a moment...")
229
230     def getUserRecordFinished(self):
231         self.disconnect(self.process, SIGNAL('finished()'), self.getUserRecordFinished)
232
233         faultString = self.process.getFaultString()
234         if not faultString:
235             self.setStatus("<font color='green'>Slice list refreshed.</font>")
236             self.updateSliceView()
237         else:
238             self.setStatus("<font color='red'>User rec refresh error: %s</font>" % (faultString))
239
240     def refreshAuthority(self):
241         self.process = SfiProcess(self)
242         self.connect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
243
244         self.process.listRecords(config.getAuthority(), None)
245         self.setStatus("Refreshing slice list. This will take a moment...")
246
247     def getAuthorityRecordFinished(self):
248         self.disconnect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
249
250         faultString = self.process.getFaultString()
251         if not faultString:
252             self.setStatus("<font color='green'>Slice list refreshed.</font>")
253             self.updateSliceView()
254         else:
255             self.setStatus("<font color='red'>Authority rec refresh error: %s</font>" % (faultString))
256
257     def updateSliceView(self):
258         self.sliceModel.updateModel()
259
260         self.sliceView.setModel(self.sliceModel)
261         self.sliceView.resizeColumnToContents(0)
262