5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
8 from sfa.util.record import SfaRecord, SliceRecord, AuthorityRecord, UserRecord
9 from sface.config import config
10 from sface.sfiprocess import SfiProcess
11 from sface.screens.sfascreen import SfaScreen
15 MEMBERSHIP_STATUS_COLUMN = 1
16 SERVER_MEMBERSHIP_STATUS_COLUMN = 2
18 # maximum length of a name to display before clipping
21 user_status = { "in": "Already Selected",
22 "out": "Not Selected",
24 "remove": "To be Removed"}
26 color_status = { "in": QColor.fromRgb(0, 250, 250),
27 "add": QColor.fromRgb(0, 250, 0),
28 "remove": QColor.fromRgb(250, 0, 0) }
31 class UserNameDelegate(QStyledItemDelegate):
32 def __init__(self, parent):
33 QStyledItemDelegate.__init__(self, parent)
35 def displayText(self, value, locale):
36 data = str(QStyledItemDelegate.displayText(self, value, locale))
37 if (len(data)>NAME_MAX_LEN):
38 data = data[:(NAME_MAX_LEN-3)] + "..."
41 def paint(self, painter, option, index):
43 data = str(self.displayText(index.data(), QLocale()))
44 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
45 status_data = status_index.data().toString()
47 fm = QFontMetrics(option.font)
48 rect = QRect(option.rect)
50 rect.setHeight(rect.height() - 2)
51 rect.setWidth(fm.width(QString(data)) + 6)
52 rect.setX(rect.x() + 5)
53 rect.setY(rect.y() - 1)
55 textRect = QRect(option.rect)
56 textRect.setWidth(fm.width(QString(data)) + 6)
57 textRect.setX(rect.x())
59 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
62 path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
65 painter.setRenderHint(QPainter.Antialiasing)
67 if option.state & QStyle.State_Selected:
68 painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
71 for x in user_status.keys():
72 if (user_status[x] == status_data) and (x in color_status):
73 color = color_status[x]
76 painter.fillPath(path, color)
77 painter.setPen(QColor.fromRgb(0, 0, 0))
78 painter.drawText(textRect, Qt.AlignVCenter, QString(data))
82 class UserView(QTableView):
83 def __init__(self, parent=None):
84 QTableView.__init__(self, parent)
86 self.setSelectionBehavior(QAbstractItemView.SelectRows)
87 self.setSelectionMode(QAbstractItemView.SingleSelection)
88 self.setAlternatingRowColors(True)
89 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
90 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
91 self.setToolTip("Double click on a row to change its status.")
93 self.setItemDelegateForColumn(0, UserNameDelegate(self))
95 def keyPressEvent(self, event):
96 if (event.key() == Qt.Key_Space):
97 self.toggleSelection()
99 QTableView.keyPressEvent(self, event)
101 def mouseDoubleClickEvent(self, event):
102 self.toggleSelection()
104 def toggleSelection(self):
105 index = self.currentIndex()
106 model = index.model()
107 status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
108 status_data = status_index.data().toString()
109 server_status_data = model.index(index.row(), SERVER_MEMBERSHIP_STATUS_COLUMN, index.parent()).data().toString()
110 node_index = model.index(index.row(), NAME_COLUMN, index.parent())
111 node_data = node_index.data().toString()
114 if status_data == user_status['in']:
115 model.setData(status_index, QString(user_status['remove']))
116 elif status_data == user_status['out']:
117 model.setData(status_index, QString(user_status['add']))
118 elif status_data in (user_status['add'], user_status['remove']):
119 if server_status_data == user_status["in"]:
120 model.setData(status_index, QString(user_status['in']))
122 model.setData(status_index, QString(user_status['out']))
124 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
126 def currentChanged(self, current, previous):
127 model = current.model()
128 node_index = model.index(current.row(), 0, current.parent())
129 node_data = node_index.data().toString()
131 def hideUnusableColumns(self):
132 self.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
134 class UserModel(QStandardItemModel):
135 def __init__(self, rows=0, columns=4, parent=None):
136 QStandardItemModel.__init__(self, rows, columns, parent)
138 def updateModel(self, sliceRec):
145 #for pi in sliceRec.get_field("PI", default=[]):
147 # if not name in added_persons:
148 # slice_persons.append({"name": name, "role": "PI", "member": user_status["in"]})
149 # added_persons.append(name)
151 for researcher in sliceRec.get_field("researcher", default=[]):
152 name = str(researcher)
153 if not name in added_persons:
154 slice_persons.append({"name": name, "role": "researcher", "member": user_status["in"]})
155 added_persons.append(name)
158 while (os.path.exists(config.getAuthorityListFile(i))):
159 rec = self.readUserRecord(i)
161 type = str(rec.get_type())
163 name = str(rec.get_name())
164 if not name in added_persons:
165 slice_persons.append({"name": name, "role": "", "member": user_status["out"]})
166 added_persons.append(name)
169 rootItem = self.invisibleRootItem()
171 for person in slice_persons:
172 rootItem.appendRow([self.readOnlyItem(person["name"]),
173 self.readOnlyItem(person["member"]),
174 self.readOnlyItem(person["member"])])
176 headers = QStringList() << "User Name" << "Status" << "ServerStatus"
177 self.setHorizontalHeaderLabels(headers)
179 def readOnlyItem(self, x):
180 item = QStandardItem(QString(x))
181 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
184 def updateRecord(self, slicerec):
187 item = self.invisibleRootItem()
188 children = item.rowCount()
189 for row in range(0, children):
190 childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
191 childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
193 if (childStatus == user_status['add']):
194 researcher = slicerec.get_field("researcher", [])
195 researcher.append(childName)
196 slicerec["researcher"] = researcher
198 elif (childStatus == user_status['remove']):
199 if childName in slicerec.get_field("PI"):
200 slicerec.get_field("PI").remove(childName)
201 if childName in slicerec.get_field("researcher"):
202 slicerec.get_field("researcher").remove(childName)
207 def getResearchers(self):
209 item = self.invisibleRootItem()
210 children = item.rowCount()
211 for row in range(0, children):
212 childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
213 childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
215 if (childStatus == user_status['add']) or (childStatus == user_status['in']):
216 researchers.append(childName)
220 def readUserRecord(self, i):
221 rec_file = config.getAuthorityListFile(i)
222 if os.path.exists(rec_file):
223 xml = open(rec_file).read()
225 rec.load_from_string(xml)
229 class UsersWidget(QWidget):
230 def __init__(self, parent):
231 QWidget.__init__(self, parent)
233 self.process = SfiProcess(self)
235 self.slicename = QLabel("", self)
236 self.updateSliceName()
237 self.slicename.setScaledContents(False)
238 searchlabel = QLabel ("Search: ", self)
239 searchlabel.setScaledContents(False)
240 searchbox = QLineEdit(self)
241 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
243 toplayout = QHBoxLayout()
244 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
245 toplayout.addStretch()
246 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
247 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
249 self.userView = UserView()
251 refresh = QPushButton("Refresh", self)
252 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
253 submit = QPushButton("Submit", self)
254 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
256 bottomlayout = QHBoxLayout()
257 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
258 bottomlayout.addStretch()
259 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
261 layout = QVBoxLayout()
262 layout.addLayout(toplayout)
263 layout.addWidget(self.userView)
264 layout.addLayout(bottomlayout)
265 self.setLayout(layout)
266 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
268 self.userModel = UserModel(parent=self)
270 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
271 self.connect(submit, SIGNAL('clicked()'), self.submit)
275 def submitFinished(self):
276 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
278 faultString = self.process.getFaultString()
280 self.setStatus("<font color='green'>Slice user data submitted.</font>")
281 QTimer.singleShot(1000, self.refresh)
283 self.setStatus("<font color='red'>Slice user submit failed: %s</font>" % (faultString))
285 def getSliceRecordFinished(self):
286 self.disconnect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
288 faultString = self.process.getFaultString()
290 self.setStatus("<font color='green'>Slice record refreshed.</font>")
291 self.refreshAuthority()
293 self.setStatus("<font color='red'>Slice rec refresh error: %s</font>" % (faultString))
295 def getAuthorityRecordFinished(self):
296 self.disconnect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
298 faultString = self.process.getFaultString()
300 self.setStatus("<font color='green'>User data refreshed.</font>")
302 #self.parent().signalAll("usersUpdated")
304 self.setStatus("<font color='red'>Authority rec refresh error: %s</font>" % (faultString))
306 def readSliceRecord(self):
307 rec_file = config.getSliceRecordFile()
308 if os.path.exists(rec_file):
309 xml = open(rec_file).read()
311 rec.load_from_string(xml)
315 def readAuthorityRecord(self):
316 rec_file = config.getAuthorityRecordFile()
317 if os.path.exists(rec_file):
318 xml = open(rec_file).read()
319 rec = AuthorityRecord()
320 rec.load_from_string(xml)
324 def setStatus(self, msg, timeout=None):
325 self.parent().setStatus(msg, timeout)
327 def checkRunningProcess(self):
328 if self.process.isRunning():
329 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
334 if self.checkRunningProcess():
337 rec = self.readSliceRecord()
338 change = self.userModel.updateRecord(rec)
341 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
344 rec_file = config.getSliceRecordFile()
345 file(rec_file, "w").write(rec.save_to_string())
347 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
349 self.process.updateRecord(rec_file)
350 self.setStatus("Sending slice record. This will take some time...")
353 if not config.getSlice():
354 self.setStatus("<font color='red'>Slice not set yet!</font>")
357 if self.process.isRunning():
358 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
361 self.connect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
363 self.process.getSliceRecord()
364 self.setStatus("Refreshing slice record. This will take some time...")
366 def refreshAuthority(self):
367 self.connect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
369 self.process.listRecords(config.getAuthority(), None, config.getAuthorityListFile())
370 self.setStatus("Refreshing user records. This will take some time...")
372 def updateView(self):
373 sliceRec = self.readSliceRecord()
376 # wait until we've resolved the slicerecord before displaying
377 # anything to the user.
378 self.userModel.clear()
380 self.userModel.updateModel(sliceRec)
382 self.userView.setModel(self.userModel)
383 self.userView.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
384 self.userView.resizeColumnToContents(0)
386 def updateSliceName(self):
387 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
392 class UserScreen(SfaScreen):
393 def __init__(self, parent):
394 SfaScreen.__init__(self, parent)
396 slice = UsersWidget(self)
397 self.init(slice, "Users", "OneLab SFA crawler")
399 def configurationChanged(self):
400 self.widget.updateSliceName()
401 self.widget.updateView()