def editReviewer(self, sort_name, prev_index): ''' select Reviewer for editing as referenced by sort_name ''' if self.details_modified(): self.saveReviewerDetails() if sort_name is None: return panelist = self.reviewers.getReviewer(str(sort_name)) self.details_panel.setFullName(panelist.getKey('full_name')) self.details_panel.setSortName(panelist.getKey('name')) self.details_panel.setPhone(panelist.getKey('phone')) self.details_panel.setEmail(panelist.getKey('email')) self.details_panel.setNotes(panelist.getKey('notes')) self.details_panel.setJoined(panelist.getKey('joined')) self.details_panel.setUrl(panelist.getKey('URL')) topics_list = panelist.getTopicList() if len(self.details_panel.topic_list) == 0: self._init_topic_widgets(panelist.topics) # need to create topic widgets first for topic in topics_list: value = panelist.getTopic(topic) self.details_panel.setTopic(topic, value) # set reviewers self.prior_selection_index = self.listView.currentIndex() self.details_panel.modified = False history.addLog('selected reviewer: ' + str(sort_name))
def onTopicValueChanged(self, topic): ''' ''' value = self.topic_widgets[topic].getValue() history.addLog("topic (" + topic + ") value changed: " + str(value)) self.modified = True prop_id = str(self.getProposalId()) self.custom_signals.topicValueChanged.emit(prop_id, str(topic), value)
def doOpenPrpFile(self): ''' open an existing PRP file ''' history.addLog('Open PRP File requested', True) if self.cannotProceed(): if self.confirmAbandonChangesNotOk(): return self.forced_exit = False # may have been set in confirmAbandonChangesNotOk() flags = QtGui.QFileDialog.DontResolveSymlinks title = 'Open PRP file' prp_file = self.settings.getPrpFile() if len(prp_file) == 0: prp_path = '' else: prp_path = os.path.dirname(prp_file) #open_cmd = QtGui.QFileDialog.getOpenFileName #filename = str(open_cmd(None, title, prp_path, AGUP_OPEN_FILTER)) filename = self.getOpenFileName(None, title, prp_path, AGUP_OPEN_FILTER) if os.path.exists(filename): self.openPrpFile(filename) history.addLog('selected PRP file: ' + filename)
def onTopicValueChanged(self, topic): ''' ''' value = self.topic_widgets[topic].getValue() history.addLog("topic (" + topic + ") value changed: " + str(value)) self.modified = True sort_name = str(self.getSortName()) self.custom_signals.topicValueChanged.emit(sort_name, str(topic), value)
def doAgupInfo(self, *args, **kw): ''' describe this application and where to get more info ''' history.addLog('Info... box requested', False) # bless the Mac that it handles "about" differently ui = about.InfoBox(self, self.settings) ui.show()
def _init_mainwindow_widget_values_(self): self.setPrpFileText(self.settings.getPrpFile()) self.setRcFileText(self.settings.fileName()) self.setReviewCycleText(self.settings.getReviewCycle()) for key in sorted(self.settings.allKeys()): value = self.settings.getKey(key) history.addLog('Configuration option: %s = %s' % (key, str(value)), False)
def doResetDefaultSettings(self): ''' user requested to reset the settings to their default values Note: does not write to the rcfile ''' history.addLog('Reset to Default Settings requested', False) self.settings.resetDefaults() self.adjustMainWindowTitle()
def _handler_(self, msg_type, msg_string): if msg_type == QtCore.QtDebugMsg: adjective = 'Debug' elif msg_type == QtCore.QtWarningMsg: adjective = 'Warning' elif msg_type == QtCore.QtCriticalMsg: adjective = 'Critical' elif msg_type == QtCore.QtFatalMsg: adjective = 'Fatal' history.addLog('QtCore.qInstallMsgHandler-' + adjective + ': ' + msg_string)
def editProposal(self, prop_id, prev_prop_index): ''' select Proposal for editing as referenced by ID number ''' if prop_id is None: return proposal = self.proposals.getProposal(str(prop_id)) self.details_panel.setupProposal(proposal) self.prior_selection_index = self.listView.currentIndex() self.details_panel.modified = False history.addLog('selected proposal: ' + str(prop_id))
def doImportReviewers(self): ''' copy the list of Reviewers into this project from another PRP Project file ''' history.addLog('Import Reviewers requested', False) title = 'Choose a PRP Project file to copy its Reviewers' prp_path = os.path.dirname(self.settings.getPrpFile()) open_cmd = QtGui.QFileDialog.getOpenFileName path = str(open_cmd(None, title, prp_path, AGUP_OPEN_FILTER)) if os.path.exists(path): self.importReviewers(path)
def doClose(self, *args, **kw): ''' called when user chooses exit (or quit), or from closeEvent() ''' history.addLog('application exit requested', False) if self.cannotProceed(): if self.confirmAbandonChangesNotOk(): return self.saveWindowGeometry() self.closeSubwindows() self.close()
def doAutomatedAssignment(self): ''' make automated assignments of reviewers to proposals ''' auto_assign = auto_assignment.Auto_Assign(self.agup) number_changed = auto_assign.simpleAssignment() history.addLog('doAutomatedAssignment() complete', False) if number_changed > 0: if self.windows[PROPOSAL_VIEW] is not None: self.windows[PROPOSAL_VIEW].close() self.windows[PROPOSAL_VIEW] = None self.modified = True self.onAssignmentsChanged()
def doImportTopics(self): ''' copy the list of Topics from another PRP file into this project ''' history.addLog('Import Topics requested', False) title = 'Choose a PRP Project file to copy its Topics' prp_path = os.path.dirname(self.settings.getPrpFile()) open_cmd = QtGui.QFileDialog.getOpenFileName filename = str(open_cmd(None, title, prp_path, AGUP_OPEN_FILTER)) if os.path.exists(filename): self.importTopics(filename) history.addLog('imported Topics from: ' + filename)
def confirmAbandonChangesNotOk(self): ''' Ask user to save changes before exit or opening another project. Return True if application should *NOT* exit. ''' ret = self.requestConfirmation('The project data has changed.', 'Save the changes?', QtGui.QMessageBox.Save | QtGui.QMessageBox.Discard | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Save) if ret == QtGui.QMessageBox.Save: history.addLog('Save before action proceeds') self.doSave() elif ret == QtGui.QMessageBox.Cancel: history.addLog('action was canceled') return True # action should NOT proceed elif ret == QtGui.QMessageBox.Discard: self.forced_exit = True history.addLog('Discard Changes before action proceeds') else: msg = 'wrong button value from confirmAbandonChangesNotOk dialog: ' + str(ret) history.addLog('ValueError: ' + msg) raise ValueError(msg) return False # application should exit
def closeEvent(self, event): ''' called when user clicks the big [X] to quit ''' history.addLog('application forced quit requested', False) if self.cannotProceed(): if self.confirmAbandonChangesNotOk(): event.ignore() else: self.doClose() event.accept() else: self.doClose() event.accept() # let the window close
def openPrpFile(self, filename): ''' ''' if not os.path.exists(filename): history.addLog('PRP File not found: ' + filename) return False self.clearAllData() filename = str(filename) self.importTopics(filename) self.importReviewers(filename) self.importProposals(filename) self.importEmailTemplate(filename) self.modified = False return True
def doLicense(self): '''show the license''' if self.license_box is None: history.addLog('opening License in new window') #history.addLog('DEBUG: ' + LICENSE_FILE) lfile = resources.resource_file(LICENSE_FILE, '.') #history.addLog('DEBUG: ' + lfile) license_text = open(lfile, 'r').read() #history.addLog('DEBUG: ' + license_text) ui = plainTextEdit.TextWindow(None, 'LICENSE', license_text, self.settings) ui.setMinimumSize(700, 500) self.license_box = ui #ui.setWindowModality(QtCore.Qt.ApplicationModal) #history.addLog('DEBUG: ' + str(ui)) self.license_box.show()
def doEditEmailTemplate(self): ''' edit the template to send emails, include editor for keyword substitutions ''' import editor_email_template history.addLog('doEditEmailTemplate() requested', False) win = self.windows[window_key] if win is None: try: win = editor_email_template.Editor(None, self.agup, self.settings) except Exception: history.addLog(traceback.format_exc()) self.windows[EMAIL_TEMPLATE_EDITOR] = win win.custom_signals.changed.connect(self.onTemplateChanged) else: win.show()
def importTopics(self, filename): '''read Topics from an AGUP PRP Project file and set the model accordingly''' try: self.agup.importTopics(filename) except Exception: history.addLog(traceback.format_exc()) self.requestConfirmation( filename + ' was not an AGUP Project file', 'Import Topics failed' ) return # self.setNumTopicsWidget(len(self.agup.topics)) self.onTopicValuesChanged('', '', 0.0) self.modified = True self.setIndicators() history.addLog('imported topics from: ' + filename)
def doLettersReport(self): ''' prepare the email form letters to each reviewer with their assignments ''' import email_mvc_view history.addLog('doLettersReport() requested', False) win = self.windows[EMAIL_REPORT] if win is None: try: win = email_mvc_view.AGUP_Emails_View(None, self.agup, self.settings) self.windows[EMAIL_REPORT] = win win.show() self.custom_signals.checkBoxGridChanged.connect(win.update) except Exception: history.addLog(traceback.format_exc()) else: win.update() win.show()
def importReviewers(self, filename): '''read Reviewers from a PRP Project file and set the model accordingly''' try: self.agup.importReviewers(filename) except Exception as exc: tb = traceback.format_exc() history.addLog(tb) summary = 'Import Reviewers failed.' msg = os.path.basename(filename) + ': ' msg += str(exc.message) self.requestConfirmation(msg, summary) # self.setNumTopicsWidget(len(self.agup.topics)) # self.setNumReviewersWidget(len(self.agup.reviewers)) self.onAssignmentsChanged() self.modified = True self.setIndicators() history.addLog('imported Reviewers from: ' + filename)
def doAssignmentsReport(self): ''' show a read-only text page with assignments for each proposal ''' import report_assignments history.addLog('doAssignmentsReport() requested', False) win = self.windows[ASSIGNMENT_REPORT] if win is None: try: win = report_assignments.Report(None, self.agup, self.settings) self.windows[ASSIGNMENT_REPORT] = win win.show() self.custom_signals.checkBoxGridChanged.connect(win.update) except Exception: history.addLog(traceback.format_exc()) else: win.update() win.show()
def doSummaryReport(self): ''' this report is helpful to balance proposal assignments show a read-only text page with how many primary and secondary proposals assigned to each reviewer ''' import report_summary history.addLog('doSummaryReport() requested', False) win = self.windows[SUMMARY_REPORT] if win is None: try: win = report_summary.Report(None, self.agup, self.settings) self.windows[SUMMARY_REPORT] = win self.custom_signals.checkBoxGridChanged.connect(win.update) except Exception: history.addLog(traceback.format_exc()) else: win.update() win.show()
def doAnalysis_gridReport(self): ''' show a table with dotProducts for each reviewer against each proposal *and* assignments ''' import report_analysis_grid history.addLog('doAnalysis_gridReport() requested', False) win = self.windows[ANALYSISGRID_REPORT] if win is None: try: win = report_analysis_grid.Report(None, self.agup, self.settings) self.windows[ANALYSISGRID_REPORT] = win win.show() self.custom_signals.checkBoxGridChanged.connect(win.update) self.custom_signals.topicValueChanged.connect(win.update) except Exception: history.addLog(traceback.format_exc()) else: win.update() win.show()
def doSaveAs(self): ''' save the self.agup data to the data file name selected from a dialog box You may choose any file name and extension that you prefer. It is strongly suggested you choose the default file extension, to identify AGUP PRP Project files more easily on disk. Multiple projects files, perhaps for different review cycles, can be saved in the same directory. Or you can save each project file in a different directory as you choose. By default, the file extension will be **.agup**, indicating that this is an AGUP PRP Project file. The extensions *.prp* or *.xml* may be used as alternatives. Each of these describes a file with *exactly the same file format*, an XML document. ''' history.addLog('Save As requested', False) filename = self.settings.getPrpFile() filename = QtGui.QFileDialog.getSaveFileName(parent=self, caption="Save the PRP project", directory=filename, filter=AGUP_filters) filename = os.path.abspath(str(filename)) if len(filename) == 0: return if os.path.isdir(filename): history.addLog('cannot save, selected a directory: ' + filename) return if os.path.islink(filename): # might need deeper analysis history.addLog('cannot save, selected a link: ' + filename) return if os.path.ismount(filename): history.addLog('cannot save, selected a mount point: ' + filename) return self.agup.write(filename) self.setPrpFileText(filename) self.modified = False for w in (self.windows[PROPOSAL_VIEW], self.windows[REVIEWER_VIEW]): if w is not None: w.details_panel.modified = False history.addLog('saved: ' + filename) self.adjustMainWindowTitle()
def doUnassignProposals(self): ''' Remove ALL assignments of reviewers to proposals ''' history.addLog('doUnassignProposals() requested', False) ret = self.requestConfirmation('Remove ALL assignments of reviewers to proposals', 'Remove ALL?', QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) if ret == QtGui.QMessageBox.Cancel: history.addLog('doUnassignProposals() was canceled', False) return counter = 0 for prop in self.agup.proposals: for full_name, value in prop.eligible_reviewers.items(): if value is not None: prop.eligible_reviewers[full_name] = None counter += 1 if counter == 0: msg = 'no assignments to be removed' else: msg = str(counter) msg += ' assignment' if counter > 1: msg += 's' msg += ' removed' self.modified = True history.addLog(msg) if self.windows[PROPOSAL_VIEW] is not None: self.windows[PROPOSAL_VIEW].close() self.windows[PROPOSAL_VIEW] = None self.onAssignmentsChanged()
def saveReviewerDetails(self): ''' copied Reviewer details from editor panel to main data structure ''' sort_name = str(self.details_panel.getSortName()) if len(sort_name) == 0: return panelist = self.reviewers.getReviewer(sort_name) # raises IndexError if not found kv = dict( full_name=self.details_panel.getFullName, name=self.details_panel.getSortName, phone=self.details_panel.getPhone, email=self.details_panel.getEmail, notes=self.details_panel.getNotes, joined=self.details_panel.getJoined, URL=self.details_panel.getUrl, ) for k, v in kv.items(): panelist.setKey(k, v()) history.addLog('saved reviewer details: ' + sort_name) self.details_panel.modified = False
def doNewPrpFile(self): ''' clear the data in self.agup ''' history.addLog('New PRP File requested', False) if self.cannotProceed(): ret = self.requestConfirmation('There are unsaved changes.', 'Forget about them?', QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel, QtGui.QMessageBox.Cancel) if ret == QtGui.QMessageBox.Cancel: return self.closeSubwindows() #self.agup.clearAllData() # TODO: Why not make a new self.agup object? self.agup = agup_data.AGUP_Data(self, self.settings) self.modified = False self.setPrpFileText('') self.setIndicators() history.addLog('New PRP File') self.adjustMainWindowTitle()
def __init__(self): self.settings = settings.ApplicationQSettings() self.agup = agup_data.AGUP_Data(self, self.settings) QtGui.QMainWindow.__init__(self) resources.loadUi(UI_FILE, baseinstance=self) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.main_window_title = self.windowTitle() self.restoreWindowGeometry() self.modified = False self.forced_exit = False # keep these objects in a dictionary to simplify admin self.windows = {} self.windows[ANALYSISGRID_REPORT] = None self.windows[ASSIGNMENT_REPORT] = None self.windows[EMAIL_REPORT] = None self.windows[EMAIL_TEMPLATE_EDITOR] = None self.windows[PROPOSAL_VIEW] = None self.windows[REVIEWER_VIEW] = None self.windows[SUMMARY_REPORT] = None self._init_history_() history.addLog('loaded "' + UI_FILE + '"', False) self.custom_signals = signals.CustomSignals() self._init_mainwindow_widget_values_() self._init_connections_() filename = self.settings.getPrpFile() if os.path.exists(filename): self.openPrpFile(filename) self.modified = False self.adjustMainWindowTitle()
def importProposals(self, filename): '''read a proposals XML file and set the model accordingly''' try: self.agup.importProposals(filename) except Exception as exc: tb = traceback.format_exc() history.addLog(tb) summary = 'Import Proposals failed.' msg = os.path.basename(filename) + ': ' msg += str(exc.message) self.requestConfirmation(msg, summary) # ensure each imported proposal has the correct Topics for prop in self.agup.proposals: added, removed = self.agup.topics.diff(prop.topics) prop.addTopics(added) prop.removeTopics(removed) self.modified = True self.setIndicators() history.addLog('imported Proposals from: ' + filename) if self.getReviewCycleText() == '': self.setReviewCycleText(self.agup.proposals.cycle)
def doImportProposals(self): ''' import the proposal file as downloaded from the APS web site ''' history.addLog('Import Proposals requested', False) title = 'Choose XML file with proposals' prp_path = os.path.dirname(self.settings.getPrpFile()) open_cmd = QtGui.QFileDialog.getOpenFileName path = str(open_cmd(None, title, prp_path, "Proposals (*.xml)")) if os.path.exists(path): history.addLog('selected file: ' + path, False) self.importProposals(path) history.addLog('imported proposals file: ' + path)
def doIssuesUrl(self): '''opening issues URL in default browser''' history.addLog('opening issues URL in default browser') self.doUrl(ISSUES_URL)
def doDocsUrl(self): '''opening documentation URL in default browser''' history.addLog('opening documentation URL in default browser') self.doUrl(DOCS_URL)
def update(self): """ """ text = self.makeText() self.setText(text) history.addLog(self.__class__.__name__ + ".update()", False)
def update(self): ''' ''' history.addLog(self.__class__.__name__ + '.update()', False)
def update(self): ''' ''' # TODO: update the checkboxes history.addLog(self.__class__.__name__ + '.update()', False)
def update(self): ''' ''' text = self.makeText() self.setText(text) history.addLog(self.__class__.__name__ + '.update()', False)
def update(self): ''' ''' history.addLog(self.__class__.__name__ + '.update()', False) grid = self.reviewers_gb.layout()
def simpleAssignment(self): ''' assign the first two reviewers with the highest scores to *unassigned* proposals * no attempt to balance assignment loads in this procedure * score must be above zero to qualify ''' def sort_reviewers(scores): ''' order the reviewers by score on this proposal ''' xref = {} for who, score in scores.items(): if score not in xref: xref[score] = [] xref[score].append(who) name_list = [] for s, names in sorted(xref.items(), reverse=True): if s > 0: name_list += names return name_list counter = 0 for prop in self.agup.proposals: # check for any existing assignments assigned = prop.getAssignedReviewers() if None not in assigned: continue # all assigned, skip this proposal scores = self.getScores(prop) # mark existing assigned reviewers to exclude further consideration, this round for role, full_name in enumerate(assigned): if full_name in prop.eligible_reviewers: scores[full_name] = SCORE_ALREADY_ASSIGNED # order the reviewers by score on this proposal for full_name in sort_reviewers(scores): role = None if assigned[0] is None: role = 0 elif assigned[1] is None: role = 1 else: break if role is not None: assigned[role] = full_name counter += 1 if None not in assigned: break # all assigned, move to next proposal for role, full_name in enumerate(assigned): prop.eligible_reviewers[full_name] = role + 1 msg = 'Auto_Assign.simpleAssignment: ' msg += str(counter) msg += ' assignment' if counter > 1: msg += 's' history.addLog(msg) return counter
def update(self): # not called history.addLog(self.__class__.__name__ + '.update()', False)