diff --git a/README.md b/README.md index a97350c..7f41af5 100644 --- a/README.md +++ b/README.md @@ -25,18 +25,24 @@ white. Each category is selected in the panel on the left. # Controls The user can navigate between panels using the Tab key and select categories -and reviewers using the space bar. These can also be navigated with the mouse. +and reviewers using the space bar. These can also be navigated with the mouse. Each column can be sorted alphabetically or numerically (depending on its content). The columns can also be positioned to the user's tastes. +#Auto-Assign +In 2022 an "Assign" menu was added to automatically pre-assign reviewers to categories. In order to use this feature, first set maximum and minimum nr of reviewers and abstracts in the "Settings" menu, then hit "Assign" in the Assign menu. + +If the parameter settings are incompatible, review the settings and hit "Assign" again. The individual steps in the assignments can also be repeated with the other buttons in the Assign menu. After auto-assigning, you can edit assignments manually as before. + +#Input data +For RevAssign to work, it is important that the input data are formatted correctly. Please see the rev_input.xls files in the example_data folder as an example. + Sessions can be saved as a `.mpc` file and later exported to a final `.xls` spreadsheet to start the next process in the AMPC work flow. # Install -The latest app for OS X can be found in [Releases](https://github.com/nckz/RevAssign/releases). - -Alternatively, RevAssign can be installed in Anaconda by running: +RevAssign can be installed in Anaconda by running: $ conda install -c https://conda.anaconda.org/nckz revassign diff --git a/RevAssign.py b/RevAssign.py index 63c509a..b26ba85 100755 --- a/RevAssign.py +++ b/RevAssign.py @@ -21,7 +21,7 @@ import time # Import Qt modules -from PyQt4 import QtCore,QtGui +from PyQt5 import QtCore,QtGui, QtWidgets # Import the compiled UI module from windowUi import Ui_MainWindow @@ -31,9 +31,9 @@ # correct the numeric sorting for specified columns of the category list -class cat_TreeWidgetItem(QtGui.QTreeWidgetItem): +class cat_TreeWidgetItem(QtWidgets.QTreeWidgetItem): def __init__(self, parent=None): - QtGui.QTreeWidgetItem.__init__(self, parent) + QtWidgets.QTreeWidgetItem.__init__(self, parent) def __lt__(self, otherItem): column = self.treeWidget().sortColumn() @@ -71,9 +71,9 @@ def __lt__(self, otherItem): # correct the numeric sorting for specified columns of the reviewer list -class rev_TreeWidgetItem(QtGui.QTreeWidgetItem): +class rev_TreeWidgetItem(QtWidgets.QTreeWidgetItem): def __init__(self, parent=None, parent_obj=None): - QtGui.QTreeWidgetItem.__init__(self, parent) + QtWidgets.QTreeWidgetItem.__init__(self, parent) # ref to parent class self.parent = parent_obj @@ -81,9 +81,9 @@ def __init__(self, parent=None, parent_obj=None): # if contents passed then decorate for sorting self.dlist = [] self.dlist.append(int(parent[0])) # member # - for i in xrange(1,12): + for i in range(1,12): self.dlist.append(str(parent[i])) - for i in xrange(12,18): + for i in range(12,18): self.dlist.append(int(parent[i])) self.dlist.append(str(parent[18])) @@ -126,7 +126,7 @@ def setAllBrushes(self): self.brush = self.background(1) #for col in self.colRange: - for col in xrange(0,self.totalCol): + for col in range(0,self.totalCol): self.setBackground(col,self.brush) @@ -197,9 +197,9 @@ def __lt__(self, otherItem): return self.text(column) < otherItem.text(column) # Create a class for our main window -class Main(QtGui.QMainWindow): +class Main(QtWidgets.QMainWindow): def __init__(self): - QtGui.QMainWindow.__init__(self) + QtWidgets.QMainWindow.__init__(self) # This is always the same self.ui=Ui_MainWindow() @@ -208,7 +208,7 @@ def __init__(self): # initialize window title self.setWindowTitle('ISMRM AMPC Reviewer Assignments') self.status = self.ui.statusbar # get handle - self.progress = QtGui.QProgressBar(self.statusBar()) + self.progress = QtWidgets.QProgressBar(self.statusBar()) self.status.addPermanentWidget(self.progress) self.hide_progress_bar() @@ -228,9 +228,12 @@ def __init__(self): self.rev_colorized = True self.cur_rev_sortIndicator = 0 self.rev_header = self.ui.revlist.header() - self.rev_abs_warnThresh = 60 + self.rev_abs_warnThresh = 56 + self.cat_abs_warnThresh = 5 + self.rev_minimum_nr_of_abstracts = 20 + self.cat_maximum_nr_of_revs = 8 - QtCore.QObject.connect(self.ui.revlist.header(), QtCore.SIGNAL("sortIndicatorChanged()"),self.revlist_header_sortIndicatorChanged) + self.ui.revlist.header().sortIndicatorChanged.connect(self.revlist_header_sortIndicatorChanged) # default column self.cur_rev_col = 1 @@ -267,7 +270,7 @@ def loadItems(self): self.disableListUpdates() # init the list widget for reviewer info - for k in self.chair.reviewers.iterkeys(): + for k in self.chair.reviewers.keys(): # create item from row info based on key item = self.createRevItem(k) @@ -279,10 +282,10 @@ def loadItems(self): self.ui.revlist.addTopLevelItem(item) # select this one - self.ui.revlist.setItemSelected(item,1) + item.setSelected(1) # init the list widget for category info - for k in self.chair.categories.iterkeys(): + for k in self.chair.categories.keys(): # create item from row info item = self.createCatItem(k) @@ -294,7 +297,7 @@ def loadItems(self): self.ui.catlist.addTopLevelItem(item) # select this one - self.ui.catlist.setItemSelected(item,1) + item.setSelected(1) # build list of cat-item references cur_item = self.ui.catlist.itemAt(0,0) @@ -320,7 +323,7 @@ def loadItems(self): # select the first item cur_item = self.ui.catlist.itemAt(0,0) self.ui.catlist.setCurrentItem(cur_item) - self.ui.catlist.setItemSelected(cur_item,self.cur_cat_col) + cur_item.setSelected(self.cur_cat_col) # set the first item as cat_key self.setCurCatKey(str(cur_item.text(0))) self.disableCatItemChanged = True @@ -354,7 +357,7 @@ def loadItems(self): # select top item cur_item = self.ui.revlist.itemAt(0,0) self.ui.revlist.setCurrentItem(cur_item) - self.ui.revlist.setItemSelected(cur_item,self.cur_rev_col) + cur_item.setSelected(self.cur_rev_col) self.cur_revkey = str(cur_item.text(0)) # redraw new info to screen @@ -380,7 +383,7 @@ def deleteAllItems(self): self.update_status('clearing memory... (reviewers)') st = time.time() self.removeItemsFromList(self.ui.revlist,self.listItemsInTree(self.ui.revlist)) - print "revlist removal time: "+str(time.time()-st) + print("revlist removal time: "+str(time.time()-st)) self.enableListUpdates() @@ -430,15 +433,15 @@ def update_progress(self, cur, total): self.progress.show() self.progress.setRange(0, total) self.progress.setValue(cur) - QtGui.QApplication.processEvents() # allow gui to update + QtWidgets.QApplication.processEvents() # allow gui to update def update_status(self, mesg): self.status.showMessage(mesg) - QtGui.QApplication.processEvents() # allow gui to update + QtWidgets.QApplication.processEvents() # allow gui to update def hide_progress_bar(self): self.progress.hide() - QtGui.QApplication.processEvents() # allow gui to update + QtWidgets.QApplication.processEvents() # allow gui to update def setHlght_AbsOverload(self,item): '''change color to light red if reviewer is or will be over the limit @@ -450,7 +453,7 @@ def setHlght_AbsOverload(self,item): abs_total = float(self.chair.reviewers[item_key][17]) if abs_total > self.rev_abs_warnThresh: col = 1 - item.setBackgroundColor(col,item.hghlghtColor[6]) + item.setBackground(col,item.hghlghtColor[6]) item.setAllBrushes() item.cur_color = 6 return @@ -464,7 +467,7 @@ def setHlght_AbsOverload(self,item): abs_total = float(self.chair.reviewers[item_key][17]) + float(self.chair.categories[self.cur_catkey][2]) if abs_total > self.rev_abs_warnThresh: col = 1 - item.setBackgroundColor(col,item.hghlghtColor[6]) + item.setBackground(col,item.hghlghtColor[6]) item.setAllBrushes() item.cur_color = 6 @@ -478,7 +481,7 @@ def setColorToWhite(self,item): #for col in item.colRange: col = 1 #for col in item.colRange: - item.setBackgroundColor(col,item.hghlghtColor[item.new_color]) + item.setBackground(col,item.hghlghtColor[item.new_color]) #item.brush.setColor(item.hghlghtColor[item.new_color]) item.setAllBrushes() # set current color @@ -494,7 +497,7 @@ def toggleColor(self,item): # for each column, change the color col = 1 #for col in item.colRange: - item.setBackgroundColor(col,item.hghlghtColor[item.new_color]) + item.setBackground(col,item.hghlghtColor[item.new_color]) #item.brush.setColor(item.hghlghtColor[item.new_color]) item.setAllBrushes() @@ -505,7 +508,7 @@ def toggleColor(self,item): self.setHlght_AbsOverload(item) def removeItemsFromList(self,tree,inlist): - print 'itemcnt:'+str(tree.topLevelItemCount()) + print('itemcnt:'+str(tree.topLevelItemCount())) cnt = tree.topLevelItemCount() tree.clear() #for i in range(0,cnt): @@ -544,7 +547,7 @@ def on_catlist_itemChanged(self,cur=None,col=None): # only care if checkbox has changed if col == 0: - print "catlist changed" + print("catlist changed") # if it was unchecked, then re-check it if not cur.checkState(0): @@ -599,17 +602,17 @@ def on_catlist_itemChanged(self,cur=None,col=None): st = time.time() self.update_status(self.sortMsg) self.ui.revlist.sortItems(self.ui.revlist.header().sortIndicatorSection(),self.ui.revlist.header().sortIndicatorOrder()) - print " -sort time: "+str(time.time()-st) + print(" -sort time: "+str(time.time()-st)) # select first item #cur_item = self.ui.revlist.itemAt(0,0) - #self.ui.revlist.setItemSelected(cur_item,self.ui.revlist.header().sortIndicatorSection()) + #cur_item.setSelected(self.ui.revlist.header().sortIndicatorSection()) #self.ui.revlist.setCurrentItem(cur_item) # redraw color st = time.time() self.refreshItemColor_rev() - print " -re-color time: "+str(time.time()-st) + print(" -re-color time: "+str(time.time()-st)) # allow update of display #self.enableListUpdates() @@ -622,7 +625,7 @@ def on_catlist_itemChanged(self,cur=None,col=None): if len(items) > 0: items[0].setSelected(False) #unselect cur_item = self.ui.revlist.itemAt(0,0) - self.ui.revlist.setItemSelected(cur_item,self.ui.revlist.header().sortIndicatorSection()) + cur_item.setSelected(self.ui.revlist.header().sortIndicatorSection()) self.ui.revlist.setCurrentItem(cur_item) @@ -639,7 +642,7 @@ def on_revlist_itemSelectionChanged(self): self.cur_revkey = str(items[0].text(0)) def revlist_header_sortIndicatorChanged(self):#,section=None,order=None): - print "revlist_header_sortIndicatorChanged triggered" + print("revlist_header_sortIndicatorChanged triggered") #print section #print order @@ -661,11 +664,11 @@ def on_revlist_itemChanged(self,cur=None,col=None): # for debugging if str(cur.text(0)) == self.cur_revkey: - print "spacebar used to select or mouse on highlighted row" + print("spacebar used to select or mouse on highlighted row") else: # shouldn't this always be the case? self.cur_revkey = str(cur.text(0)) - print "mouse checked non-highlighted row" + print("mouse checked non-highlighted row") # if it IS checked if cur.checkState(0): @@ -674,21 +677,21 @@ def on_revlist_itemChanged(self,cur=None,col=None): try: self.chair.categories[self.cur_catkey] except: - print "ERROR: please choose a category" + print("ERROR: please choose a category") return # test reviewer selection try: self.chair.reviewers[self.cur_revkey] except: - print "ERROR: please choose a reviewer" + print("ERROR: please choose a reviewer") return - print "item checked, col:"+str(col) + print("item checked, col:"+str(col)) self.chair.addRev(self.cur_revkey,self.cur_catkey) else: - print "item not checked, col:"+str(col) + print("item not checked, col:"+str(col)) self.chair.removeRev(self.cur_revkey,self.cur_catkey) # refresh row data @@ -716,6 +719,11 @@ def getItemFromRevKey(self,key): if item.text(0) == key: return item + def getItemFromCatKey(self,key): + for item in self.cat_items: + if item.text(0) == key: + return item + def refreshDisplay(self): '''update the new tallied items in each list i.e.: @@ -733,10 +741,10 @@ def refreshDisplay(self): item_r = self.cur_cat_item # set # of assigned reviewers item_r.setText(3,self.chair.categories[self.cur_catkey][3]) - print item_r.text(3) + print(item_r.text(3)) # assigned reviewers item_r.setText(5,str(self.chair.categories[self.cur_catkey][5])) - print item_r.text(5) + print(item_r.text(5)) # display rev #item_c = self.ui.revlist.selectedItems()[0] @@ -744,11 +752,11 @@ def refreshDisplay(self): # set # of assigned reviewers item_c.setText(17,self.chair.reviewers[self.cur_revkey][17]) item_c.dlist[17] = int(self.chair.reviewers[self.cur_revkey][17]) - print item_c.text(17) + print(item_c.text(17)) # assigned reviewers item_c.setText(18,str(self.chair.reviewers[self.cur_revkey][18])) item_c.dlist[18] = str(self.chair.reviewers[self.cur_revkey][18]) - print item_c.text(18) + print(item_c.text(18)) # update color after tables have been modified self.toggleColor(item_c) @@ -757,17 +765,111 @@ def refreshDisplay(self): #self.enableListUpdates() self.disableRevItemChanged = False + + def refreshDisplayAllRows(self): + '''update all items in each list + i.e.: + -number of abstracts + -number of revs in this category + ''' + + # disable update of display + self.disableRevItemChanged = True + + # display cat + i = 0 + cnt = len(self.chair.categories) + for key, cat in self.chair.categories.items(): + self.update_progress(i, cnt) + item_r = self.getItemFromCatKey(key) + # set # of assigned reviewers + item_r.setText(3, cat[3]) + # assigned reviewers + item_r.setText(5, str(cat[5])) + i += 1 + + # display rev + i = 0 + cnt = len(self.chair.reviewers) + for key, rev in self.chair.reviewers.items(): + self.update_progress(i, cnt) + item_c = self.getItemFromRevKey(key) + # set # of assigned reviewers + item_c.setText(17, rev[17]) + item_c.dlist[17] = int(rev[17]) + # assigned reviewers + item_c.setText(18,str(rev[18])) + item_c.dlist[18] = str(rev[18]) + # update color after tables have been modified + self.toggleColor(item_c) + i += 1 + + self.hide_progress_bar() + + # allow update of display + self.disableRevItemChanged = False + def on_actionAbstractLimit_triggered(self,checked=None): # this always has to be checked for triggered actions # to keep from running double actions if checked is None: return - text, ok = QtGui.QInputDialog.getText(self, 'Reviewer Abstract Warning Limit', 'Enter Limit (Cur:'+str(int(self.rev_abs_warnThresh))+'):') + text, ok = QtWidgets.QInputDialog.getText(self, + 'Maximum abstracts per reviewer', + 'Maximum abstracts per reviewer (Currently: '+str(int(self.rev_abs_warnThresh))+'):') if ok: if str(text).isdigit(): self.ui.revlist.setDisabled(True) self.rev_abs_warnThresh = float(text) - print "rev_abs_warnThresh set to: "+str(self.rev_abs_warnThresh) + print("rev_abs_warnThresh set to: "+str(self.rev_abs_warnThresh)) + self.refreshItemColor_rev() + self.ui.revlist.setDisabled(False) + + def on_actionAbstractLowerLimit_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + text, ok = QtWidgets.QInputDialog.getText(self, + 'Minimum abstracts per reviewer', + 'Minimum abstracts per reviewer (Currently: '+str(int(self.rev_minimum_nr_of_abstracts))+'):') + if ok: + if str(text).isdigit(): + self.ui.revlist.setDisabled(True) + self.rev_minimum_nr_of_abstracts = float(text) + print("rev_minimum_nr_of_abstracts set to: "+str(self.rev_minimum_nr_of_abstracts)) + self.refreshItemColor_rev() + self.ui.revlist.setDisabled(False) + + def on_actionReviewerLimit_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + text, ok = QtWidgets.QInputDialog.getText(self, + 'Minimum reviewers per abstract', + 'Minimum reviewers per abstract (Currently: '+str(int(self.cat_abs_warnThresh))+'):') + if ok: + if str(text).isdigit(): + self.ui.revlist.setDisabled(True) + self.cat_abs_warnThresh = float(text) + print("cat_abs_warnThresh set to: "+str(self.cat_abs_warnThresh)) + self.refreshItemColor_rev() + self.ui.revlist.setDisabled(False) + + def on_actionReviewerUpperLimit_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + text, ok = QtWidgets.QInputDialog.getText(self, + 'Maximum reviewers per abstract', + 'Maximum reviewers per abstract (Currently: '+str(int(self.cat_maximum_nr_of_revs))+'):') + if ok: + if str(text).isdigit(): + self.ui.revlist.setDisabled(True) + self.cat_maximum_nr_of_revs = float(text) + print("cat_maximum_nr_of_revs set to: "+str(self.cat_maximum_nr_of_revs)) self.refreshItemColor_rev() self.ui.revlist.setDisabled(False) @@ -818,12 +920,12 @@ def on_actionSaveSession_triggered(self,checked=None): # check if session is already active if not self.sessionActive: - print "ERROR: no active session to save" + print("ERROR: no active session to save") return # open file browser - fname = QtGui.QFileDialog.getSaveFileName(self, 'Save Session (*.mpc)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.mpc)') - print fname + fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Save Session (*.mpc)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.mpc)') + print(fname) # check for empty strings, if canceled if not fname: @@ -844,12 +946,12 @@ def on_actionExport_triggered(self,checked=None): # check if session is already active if not self.sessionActive: - print "ERROR: no active session to export" + print("ERROR: no active session to export") return # open file browser - fname = QtGui.QFileDialog.getSaveFileName(self, 'Export Session (*.xls)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.xls)') - print fname + fname, _ = QtWidgets.QFileDialog.getSaveFileName(self, 'Export Session (*.xls)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.xls)') + print(fname) # check for empty strings, if canceled if not fname: @@ -864,8 +966,8 @@ def on_actionOpenSession_triggered(self,checked=None): if checked is None: return # open file browser - fname = QtGui.QFileDialog().getOpenFileName(self, 'Open Session (*.mpc)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.mpc)') - print fname + fname, _ = QtWidgets.QFileDialog().getOpenFileName(self, 'Open Session (*.mpc)', os.path.expanduser('~/'), filter='AMPC Chair Session (*.mpc)') + print(fname) # check for empty strings, if canceled if not fname: @@ -885,10 +987,10 @@ def on_actionOpenSession_triggered(self,checked=None): # resize window to saved state if self.chair.state: - print "restore mainwindow state:" - print self.restoreGeometry(self.chair.state[0]) - print "restore splitter state:" - print self.ui.splitter.restoreState(self.chair.state[1]) + print("restore mainwindow state:") + print(self.restoreGeometry(self.chair.state[0])) + print("restore splitter state:") + print(self.ui.splitter.restoreState(self.chair.state[1])) # load display self.loadItems() @@ -903,8 +1005,8 @@ def on_actionOpenSpreadsheet_triggered(self,checked=None): if checked is None: return # open file browser - fname = QtGui.QFileDialog.getOpenFileName(self, 'Open Spreadsheet (*.xls)',os.path.expanduser('~/'), filter='AMPC Chair Session (*.xls)') - print fname + fname, _ = QtWidgets.QFileDialog.getOpenFileName(self, 'Open Spreadsheet (*.xls)',os.path.expanduser('~/'), filter='AMPC Chair Session (*.xls)') + print(fname) # check for empty strings, if canceled if not fname: @@ -929,7 +1031,180 @@ def on_actionOpenSpreadsheet_triggered(self,checked=None): self.sessionActive = True def on_actionQuit_triggered(self): - QtGui.QApplication.closeAllWindows() + QtWidgets.QApplication.closeAllWindows() + + def on_actionAssign_triggered(self,checked=None): + """Assign based on all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + # Assign all reviewers until the minimum per category is reached + self.on_actionAssignAllReviewers_triggered(checked=checked) + self.on_actionAssignReserves_triggered(checked=checked) + + def on_actionAssignAllReviewers_triggered(self,checked=None): + """Assign based on all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + # Assign all reviewers until the minimum per category is reached + min_required = self.cat_abs_warnThresh + for req in range(int(min_required)): + self.cat_abs_warnThresh = 1+req + for level in [0,2,1]: + for choice in range(5): + self.assignByLabel(1+choice, level) + self.cat_abs_warnThresh = min_required + + self.update_status('Finished assigning reviewers') + + def on_actionAssignReserves_triggered(self,checked=None): + """Assign based on all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + # Assign reserves until the maximum per category is reached + min_required = self.cat_abs_warnThresh + max_required = self.cat_maximum_nr_of_revs + for req in range(int(max_required)): + self.cat_abs_warnThresh = 1+req + for level in [0,2,1]: + for choice in range(5): + self.assignByLabel(1+choice, level, assign_reserves=True) + self.cat_abs_warnThresh = min_required + + self.update_status('Finished assigning reviewers') + + def on_actionClearAssignments_triggered(self,checked=None): + """Clear all assignments.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + for it in range(4): # Hack - needs to be done 4 times for some reason + i = 0 + cnt = len(self.chair.categories) + for catkey, cat in self.chair.categories.items(): + self.update_progress(i, cnt) + assigned_revs = cat[5] + for revkey in assigned_revs: + self.chair.removeRev(revkey, catkey) + i += 1 + self.hide_progress_bar() + + # refresh row data + self.refreshDisplayAllRows() + + def on_actionAssignAllLabels_triggered(self,checked=None): + """Assign based on all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + for choice in range(5): + self.assignByLabel(1+choice, 0) + + def on_actionAssignAllReviewCategories_triggered(self,checked=None): + """Assign based on review categories of all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + for choice in range(5): + self.assignByLabel(1+choice, 2) + + def on_actionAssignAllSubmissionCategories_triggered(self,checked=None): + """Assign based on review categories of all choices.""" + + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + for choice in range(5): + self.assignByLabel(1+choice, 1) + + def on_actionAssignFirst_triggered(self,checked=None): + if checked is None: return + + self.assignByLabel(1, 0) + + def on_actionAssignSecond_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + self.assignByLabel(2, 0) + + def on_actionAssignThird_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + self.assignByLabel(3, 0) + + def on_actionAssignFourth_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + self.assignByLabel(4, 0) + + def on_actionAssignFifth_triggered(self,checked=None): + # this always has to be checked for triggered actions + # to keep from running double actions + if checked is None: return + + self.assignByLabel(5, 0) + + def assignByLabel(self, nr_choice, level, assign_reserves=False): + + intro = 'Assigning reviewer '+ str(self.cat_abs_warnThresh) + if level == 0: + self.update_status(intro + ' by label ' + str(nr_choice) ) + elif level == 1: + self.update_status(intro + ' by submission category ' + str(nr_choice) ) + elif level == 2: + self.update_status(intro + ' by review category ' + str(nr_choice) ) + + # Check each of the categories + # If the category needs more reviewers, get candidate reviewers + # Assign candidate reviewers in order of priority + # Until the category has the maximum nr of reviewers. + + i = 0 + cnt = len(self.chair.categories) + cat_updated = False + for catkey, cat in self.chair.categories.items(): + self.update_progress(i, cnt) + nr_assigned_revs = len(cat[5]) + if nr_assigned_revs < self.cat_abs_warnThresh: + revkeys = self.chair.rev_candidates( + self.rev_abs_warnThresh, + cat, nr_choice, level) + if assign_reserves: + revkeys = self.chair.select_rev_reserves(revkeys, + self.rev_minimum_nr_of_abstracts) + revkeys = self.chair.prioritize_revs(revkeys) + for revkey in revkeys: + self.chair.addRev(revkey, catkey) + nr_assigned_revs += 1 + cat_updated = True + if nr_assigned_revs == self.cat_abs_warnThresh: + break # go to the next category + i += 1 + self.hide_progress_bar() + + # refresh row data + if cat_updated: self.refreshDisplayAllRows() def on_actionAbout_triggered(self,checked=None): if checked is None: return @@ -937,11 +1212,11 @@ def on_actionAbout_triggered(self,checked=None): "\tAuthors: "+str(', '.join(__authors__))+"\n" \ "\tDate: "+str(__date__)+"\n\n" \ "URL: https://github.com/nckz/RevAssign#revassign" - mb = QtGui.QMessageBox.about(self, "About RevAssign", msg) + mb = QtWidgets.QMessageBox.about(self, "About RevAssign", msg) def on_actionWorkflow_triggered(self,checked=None): if checked is None: return - mb = QtGui.QMessageBox() + mb = QtWidgets.QMessageBox() mb.setSizeGripEnabled(True) mb.about(self,"Help", \ "The ISMRM AMPC Chair interface is designed to help the chairperson assign reviewers to categories based on the reviewer's preferences, qualifications, publications, etc... "+ \ @@ -954,13 +1229,14 @@ def on_actionWorkflow_triggered(self,checked=None): "-the specific column labels are not important, the first header-row is skipped and the last abstract total row is skipped, categories may only be split into two (i.e. A & B).\n\n"+ \ "A session simply consists of assigning reviewers to each category using the check boxes and the sorting capabilities embedded in each column header. "+ \ "The headers and spreadsheet panes can be resized. The columns can also be moved left or right. "+ \ + "Since 2022, an automatic pre-assignment feature is available via the Assign menu which also requires a 3d sheet with labels and review/submission categories. "+ \ "If needed, the session can be saved and resumed later by saving to a .mpc file using the save session dialog. "+ \ "After all categories have been assigned, the final data are exported (via the Export dialog) back to an Excel spreadsheet with similar format to the input.") def main(): # Again, this is boilerplate, it's going to be the same on # almost every app you write - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) window=Main() window.show() window.raise_() diff --git a/backend.py b/backend.py index f6bc30d..cd7b33f 100644 --- a/backend.py +++ b/backend.py @@ -5,7 +5,6 @@ Summary: Backend interface for AMPC Chair person's spreadsheet editing tasks. """ - __authors__ = ["Nick Zwart","Jim Pipe"] __date__ = "2011dec19" __version__ = "r2832" @@ -21,6 +20,7 @@ class AMPCchair_data: def __init__(self): self.rev_sheet = None # reviewer workbook info self.cat_sheet = None # category workbook info + self.lab_sheet = None # category workbook info # window state self.state = None @@ -36,45 +36,28 @@ def deleteAll(self): #del self.cat_sheet del self.reviewers del self.categories + del self.labels def read_rev(self,fn='reviewer.xls'): '''read the spreadsheet data given the supplied Excel filename ''' book = xlrd.open_workbook(fn) self.rev_sheet = book.sheets()[0] # only the first sheet - return(self.rev_sheet) # return ref to sheet + return self.rev_sheet # return ref to sheet def read_cat(self,fn='category.xls'): '''read the spreadsheet data given the supplied Excel filename ''' book = xlrd.open_workbook(fn) self.cat_sheet = book.sheets()[0] # only the first sheet - return(self.cat_sheet) # return ref to sheet + return self.cat_sheet # return ref to sheet - def format_catlist(self,inlist): - '''take the input list, and enforce cell format - -all are strings, member numbers and category choices should - be changed to ints before strings - -category number (some are ints and others are alpha-numeric), col 0 - -category name, col 1 - -num abs, col 2 + def read_lab(self,fn='label.xls'): + '''read the spreadsheet data given the supplied Excel filename ''' - outlist = [] - # category number - try: - outlist.append(str(int(float(inlist[0])))) - except: - outlist.append(str(inlist[0])) - # name - outlist.append(str(inlist[1])) - # num abstracts - outlist.append(str(int(float(inlist[2])))) - # add extra columns for # assigned revs, pool, and assigned revs if needed - while len(outlist) < 6: - outlist.append(str(0)) - # set keylist to a list - outlist[5] = [] - return(outlist) + book = xlrd.open_workbook(fn) + self.lab_sheet = book.sheets()[0] # only the first sheet + return self.lab_sheet # return ref to sheet def format_revlist(self,inlist): '''take the input list, and enforce cell format @@ -90,7 +73,7 @@ def format_revlist(self,inlist): for i in range(1,12): # check all chars in each string - if type(inlist[i]) == str or type(inlist[i]) == unicode: + if type(inlist[i]) == bytes or type(inlist[i]) == str: cell = [] for c in inlist[i]: try: @@ -117,17 +100,56 @@ def format_revlist(self,inlist): outlist[18] = [] return(outlist) + def format_catlist(self,inlist): + '''take the input list, and enforce cell format + -all are strings, member numbers and category choices should + be changed to ints before strings + -category number (some are ints and others are alpha-numeric), col 0 + -category name, col 1 + -num abs, col 2 + ''' + outlist = [] + # category number + try: + outlist.append(str(int(float(inlist[0])))) + except: + outlist.append(str(inlist[0])) + # name + outlist.append(str(inlist[1])) + # num abstracts + outlist.append(str(int(float(inlist[2])))) + # add extra columns for # assigned revs, pool, and assigned revs if needed + while len(outlist) < 6: + outlist.append(str(0)) + # set keylist to a list + outlist[5] = [] + return(outlist) + + def format_lablist(self,inlist): + '''take the input list, and enforce cell format + -all are strings. + ''' + # label + # submission category + # review category + outlist = [] + for item in inlist: + text = str(item).strip() + outlist.append(text) + return outlist + def read(self,fn): #='abstassn.xls'): '''read both reviewer and category sheets from a single book ''' try: book = xlrd.open_workbook(fn) except: - print 'ERROR: invalid xls file' + print('ERROR: invalid xls file') return 1 sheets = book.sheets() self.rev_sheet = sheets[0] # only the first sheet self.cat_sheet = sheets[1] # only the second sheet + self.lab_sheet = sheets[2] # only the third sheet # REVIEWERS # make a dictionary for reviewers based on member number @@ -138,7 +160,7 @@ def read(self,fn): #='abstassn.xls'): self.reviewers = [] bad_cnt = 0 read_cnt = 0 - for i in range(0,self.rev_sheet.nrows): + for i in range(1,self.rev_sheet.nrows): # filter out non-compliant rows try: mem_info = self.format_revlist(self.rev_sheet.row_values(i)) @@ -147,11 +169,11 @@ def read(self,fn): #='abstassn.xls'): read_cnt += 1 except: bad_cnt += 1 - print 'bad_cnt:'+str(bad_cnt) - print self.rev_sheet.row_values(i) + print('bad_cnt:'+str(bad_cnt)) + print(self.rev_sheet.row_values(i)) #pass # just skip rows with no member number - print "REVIEWERS: read:"+str(read_cnt) - print "REVIEWERS: unreadable rows:"+str(bad_cnt) + print("REVIEWERS: read:"+str(read_cnt)) + print("REVIEWERS: unreadable rows:"+str(bad_cnt)) self.reviewers = dict(self.reviewers) # CATEGORIES @@ -160,7 +182,7 @@ def read(self,fn): #='abstassn.xls'): self.categories = [] bad_cnt = 0 read_cnt = 0 - for i in range(0,self.cat_sheet.nrows): # drop last row + for i in range(1,self.cat_sheet.nrows): # filter out non-compliant rows try: cur_row = self.format_catlist(self.cat_sheet.row_values(i)) @@ -182,19 +204,47 @@ def read(self,fn): #='abstassn.xls'): self.categories.append((cur_row[0],cur_row)) read_cnt += 1 - else: bad_cnt += 1 - print self.cat_sheet.row_values(i) + print(self.cat_sheet.row_values(i)) except: # tally non-readable rows bad_cnt += 1 - print self.cat_sheet.row_values(i) - print "CATEGORIES: read:"+str(read_cnt) - print "CATEGORIES: unreadable/main-category rows:"+str(bad_cnt) + print(self.cat_sheet.row_values(i)) + print("CATEGORIES: read:"+str(read_cnt)) + print("CATEGORIES: unreadable/main-category rows:"+str(bad_cnt)) self.categories = dict(self.categories) + # LABELS + # dictionary and number of labels + self.num_labels = 0 + self.labels = [] + bad_cnt = 0 + read_cnt = 0 + for i in range(1, self.lab_sheet.nrows): + # filter out non-compliant rows + try: + cur_row = self.format_lablist(self.lab_sheet.row_values(i)) + + # Valid rows have three non-zero elements (label, submission category, review category) + # check that the rule applies + if len(cur_row) == 3: + self.num_labels += 1 + self.labels.append((cur_row[0],cur_row)) + read_cnt += 1 + else: + bad_cnt += 1 + print(self.lab_sheet.row_values(i)) + + except: + # tally non-readable rows + bad_cnt += 1 + print(self.lab_sheet.row_values(i)) + print("LABELS: read:"+str(read_cnt)) + print("LABELS: unreadable/main-label rows:"+str(bad_cnt)) + self.labels = dict(self.labels) + # calculate the number of reviewers for each cat self.calcReviewPools() @@ -208,14 +258,14 @@ def calcReviewPools(self): the correct number first. ''' # for each reviewer - for k,v in self.reviewers.iteritems(): + for k,v in self.reviewers.items(): # for each choice for choice in v[12:17]: # determine if choice is empty if choice.isdigit(): # try the num, numA, and numB # also add leading zeros, up to 2, 0 or 00 - choices = [choice,choice+'A',choice+'B'] + choices = [choice,choice+'A',choice+'B',choice+'C',choice+'D',choice+'E',choice+'F',choice+'G'] choices_00 = [] for c in choices: choices_00.append(c) @@ -387,7 +437,7 @@ def addCatHeader2Sheet(self,sheet_in): def dict2List(self,dict_in): '''flatten dictionary elements to string elements in a list''' outlist = [] - for v in dict_in.itervalues(): + for v in dict_in.values(): rowlist = [] # ensure each element of a row is a string for i in v: @@ -439,7 +489,7 @@ def readSession(self,fn): try: group = pickle.load(fileptr) except: - print 'ERROR: invalid pickle file' + print('ERROR: invalid pickle file') fileptr.close() return 1 @@ -449,8 +499,8 @@ def readSession(self,fn): # load data into class hdr = group[0] if hdr[0] == 'ISMRM AMPC Chair Session': - print 'valid ISMRM AMPC Chair Session found' - print '\t-file created with version '+str(hdr[1]) + print('valid ISMRM AMPC Chair Session found') + print('\t-file created with version '+str(hdr[1])) self.reviewers = group[1] self.categories = group[2] if group[0][2] == 'no state info': @@ -458,10 +508,94 @@ def readSession(self,fn): else: self.state = group[0][2] # window state else: - print "ERROR: the chosen file is not an ISMRM AMPC Chair session" + print("ERROR: the chosen file is not an ISMRM AMPC Chair session") return 0 + def rev_candidates(self, max, cat, nr_choice, level): + + # A reviewer is a candidate for the category if: + # (i) They have less than the minimal number required + # (ii) adding the category does not give them too many abstracts to review. + # (iii) they have chosen the category; + # (iv) they are not yet assigned to the category; + + nr_cat_abstracts = cat[2] + assigned_revs = cat[5] + candidates = [] + for revkey, rev in self.reviewers.items(): + nr_rev_abstracts = rev[17] + if int(nr_rev_abstracts) + int(nr_cat_abstracts) <= max: + if self.found_match(rev[11 + nr_choice], cat, level): + if revkey not in assigned_revs: + candidates.append(revkey) + return candidates + + def select_rev_reserves(self, revkeys, threshold): + + if revkeys == []: return revkeys + + reserves = [] + for key in revkeys: + rev = self.reviewers[key] + nr_rev_abstracts = rev[17] + if int(nr_rev_abstracts) < threshold: + reserves.append(key) + return reserves + + def prioritize_revs(self, revkeys): + + if revkeys == []: return revkeys + + # Build criteria for prioritisation + # Experience and nr abstracts already assigned + experience = [] + nr_abstracts_assigned = [] + for revkey in revkeys: + rev = self.reviewers[revkey] + experience.append(self.measure_experience(rev)) + nr_abstracts_assigned.append(int(rev[17])) + + # Sort according to priorities + # First by experience (more experience = prioritised) + # For those with equal experience, prioritise those with less abstracts + paired = zip(experience, nr_abstracts_assigned, revkeys) + paired_sorted = sorted(paired, key = lambda x: (-x[0], x[1])) + _, _ , revkeys = zip(*paired_sorted) + return revkeys + + def measure_experience(self, rev): + + journal_articles = rev[10] + if journal_articles == '0-5': + return 1 + if journal_articles == '6-15': + return 2 + if journal_articles == '>15': + return 3 + def get_choice_label(self, choice): + """get the category label for a given reviewer choice""" + + for key, cat in self.categories.items(): + if int(choice) == int(key[:3]): + return cat[1] + + def found_match(self, choice, cat, level): + """Check if the reviewer's choice matches the category + at the level of label (0), submission category (1) or review category (2).""" + + if level == 0: + rev_category = int(choice) + cat_category = int(cat[0][:3]) + else: + rev_label = self.get_choice_label(choice) + cat_label = cat[1] + if rev_label is None: + return False # if none of the abstracts carry the chosen label, it is not in the list + rev_category = self.labels[rev_label][level] + cat_category = self.labels[cat_label][level] + + return rev_category == cat_category diff --git a/example_data/final_output.xls b/example_data/2015final_output.xls similarity index 100% rename from example_data/final_output.xls rename to example_data/2015final_output.xls diff --git a/example_data/mid_session.mpc b/example_data/2015mid_session.mpc similarity index 100% rename from example_data/mid_session.mpc rename to example_data/2015mid_session.mpc diff --git a/example_data/rev_input.xls b/example_data/2015rev_input.xls similarity index 98% rename from example_data/rev_input.xls rename to example_data/2015rev_input.xls index e32a5de..d945889 100644 Binary files a/example_data/rev_input.xls and b/example_data/2015rev_input.xls differ diff --git a/example_data/2022final_output.xls b/example_data/2022final_output.xls new file mode 100644 index 0000000..6a16f2e Binary files /dev/null and b/example_data/2022final_output.xls differ diff --git a/example_data/2022mid_session.mpc b/example_data/2022mid_session.mpc new file mode 100644 index 0000000..1282708 Binary files /dev/null and b/example_data/2022mid_session.mpc differ diff --git a/example_data/2022rev_input.xls b/example_data/2022rev_input.xls new file mode 100644 index 0000000..dcbc8ec Binary files /dev/null and b/example_data/2022rev_input.xls differ diff --git a/utils/findReplaceMPC.py b/utils/findReplaceMPC.py deleted file mode 100755 index 9d91559..0000000 --- a/utils/findReplaceMPC.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/Applications/RevAssign.app/Contents/Resources/miniconda/bin/python -# 1. Treat the mpc file as a long string. -# 2. Find and replace a segment of that string with given args. -# 3. Save to a new mpc file. - -import os -import sys -import pickle -import xlrd # tools for reading spreadsheets -import xlwt # tools for writing spreadsheets -import PyQt4 -from PyQt4 import QtCore,QtGui - -usage='usage: '+sys.argv[0] + ''' - - Outputs a new mpc file named _replaced.mpc -''' - -if ('--help' in sys.argv) or ('-h' in sys.argv): - print(usage) - sys.exit(0) - -if len(sys.argv) < 4: - print(usage) - sys.exit(0) - -infile = sys.argv[1] -path = os.path.dirname(infile) -outfile,_ = os.path.splitext(os.path.basename(infile)) -outfile = os.path.join(path, outfile+'_replaced.mpc') -search = sys.argv[2] -replace = sys.argv[3] - -with open(infile, 'rb') as f: - p = pickle.load(f) - s = str(p) - print '\n' - print "The number of matching instances of \'{}\': {}".format(search, s.count(search)) - print "Here are the matches:" - for l in s.split(): - if l.count(search): - print '\t', l - print '\n' - r = s.replace(search, replace) - d = eval(r,globals(), locals()) - - with open(outfile, 'wb') as o: - print "writing output to: {}".format(outfile) - pickle.dump(d,o) diff --git a/windowUi.py b/windowUi.py index c5dd84b..1f14ec5 100644 --- a/windowUi.py +++ b/windowUi.py @@ -7,7 +7,7 @@ # # WARNING! All changes made in this file will be lost! -from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtWidgets try: _fromUtf8 = QtCore.QString.fromUtf8 @@ -18,15 +18,15 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(825, 651) - self.centralwidget = QtGui.QWidget(MainWindow) + self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) - self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget) + self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.splitter = QtGui.QSplitter(self.centralwidget) + self.splitter = QtWidgets.QSplitter(self.centralwidget) self.splitter.setOrientation(QtCore.Qt.Horizontal) self.splitter.setObjectName(_fromUtf8("splitter")) - self.catlist = QtGui.QTreeWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + self.catlist = QtWidgets.QTreeWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.catlist.sizePolicy().hasHeightForWidth()) @@ -37,8 +37,8 @@ def setupUi(self, MainWindow): self.catlist.setAllColumnsShowFocus(True) self.catlist.setObjectName(_fromUtf8("catlist")) self.catlist.header().setDefaultSectionSize(100) - self.revlist = QtGui.QTreeWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + self.revlist = QtWidgets.QTreeWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.revlist.sizePolicy().hasHeightForWidth()) @@ -46,122 +46,190 @@ def setupUi(self, MainWindow): self.revlist.setBaseSize(QtCore.QSize(500, 500)) self.revlist.setToolTip(_fromUtf8("")) self.revlist.setAlternatingRowColors(False) - self.revlist.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) + self.revlist.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.revlist.setRootIsDecorated(False) self.revlist.setObjectName(_fromUtf8("revlist")) self.revlist.header().setCascadingSectionResizes(False) self.revlist.header().setDefaultSectionSize(100) self.horizontalLayout.addWidget(self.splitter) MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtGui.QMenuBar(MainWindow) + self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 825, 20)) self.menubar.setObjectName(_fromUtf8("menubar")) - self.menuFile = QtGui.QMenu(self.menubar) + self.menuFile = QtWidgets.QMenu(self.menubar) self.menuFile.setObjectName(_fromUtf8("menuFile")) - self.menuSorting = QtGui.QMenu(self.menubar) + self.menuSorting = QtWidgets.QMenu(self.menubar) self.menuSorting.setObjectName(_fromUtf8("menuSorting")) - self.menuHelp = QtGui.QMenu(self.menubar) + self.menuAssign = QtWidgets.QMenu(self.menubar) + self.menuAssign.setObjectName(_fromUtf8("menuAssign")) + self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName(_fromUtf8("menuHelp")) MainWindow.setMenuBar(self.menubar) - self.statusbar = QtGui.QStatusBar(MainWindow) + self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) - self.actionOpenSpreadsheet = QtGui.QAction(MainWindow) + self.actionOpenSpreadsheet = QtWidgets.QAction(MainWindow) self.actionOpenSpreadsheet.setObjectName(_fromUtf8("actionOpenSpreadsheet")) - self.actionOpenSession = QtGui.QAction(MainWindow) + self.actionOpenSession = QtWidgets.QAction(MainWindow) self.actionOpenSession.setObjectName(_fromUtf8("actionOpenSession")) - self.actionSaveSession = QtGui.QAction(MainWindow) + self.actionSaveSession = QtWidgets.QAction(MainWindow) self.actionSaveSession.setObjectName(_fromUtf8("actionSaveSession")) - self.actionExport = QtGui.QAction(MainWindow) + self.actionExport = QtWidgets.QAction(MainWindow) self.actionExport.setObjectName(_fromUtf8("actionExport")) - self.actionQuit = QtGui.QAction(MainWindow) + self.actionQuit = QtWidgets.QAction(MainWindow) self.actionQuit.setObjectName(_fromUtf8("actionQuit")) - self.actionReviewerChoice = QtGui.QAction(MainWindow) + self.actionAssign = QtWidgets.QAction(MainWindow) + self.actionAssign.setObjectName(_fromUtf8("actionAssign")) + self.actionAssignAllReviewers = QtWidgets.QAction(MainWindow) + self.actionAssignAllReviewers.setObjectName(_fromUtf8("actionAssignAllReviewers")) + self.actionAssignReserves = QtWidgets.QAction(MainWindow) + self.actionAssignReserves.setObjectName(_fromUtf8("actionAssignReserves")) + self.actionClearAssignments = QtWidgets.QAction(MainWindow) + self.actionClearAssignments.setObjectName(_fromUtf8("actionClearAssignments")) + self.actionAssignAllLabels = QtWidgets.QAction(MainWindow) + self.actionAssignAllLabels.setObjectName(_fromUtf8("actionAssignAllLabels")) + self.actionAssignAllReviewCategories = QtWidgets.QAction(MainWindow) + self.actionAssignAllReviewCategories.setObjectName(_fromUtf8("actionAssignAllReviewCategories")) + self.actionAssignAllSubmissionCategories = QtWidgets.QAction(MainWindow) + self.actionAssignAllSubmissionCategories.setObjectName(_fromUtf8("actionAssignAllSubmissionCategories")) + self.actionAssignFirst = QtWidgets.QAction(MainWindow) + self.actionAssignFirst.setObjectName(_fromUtf8("actionAssignFirst")) + self.actionAssignSecond = QtWidgets.QAction(MainWindow) + self.actionAssignSecond.setObjectName(_fromUtf8("actionAssignSecond")) + self.actionAssignThird = QtWidgets.QAction(MainWindow) + self.actionAssignThird.setObjectName(_fromUtf8("actionAssignThird")) + self.actionAssignFourth = QtWidgets.QAction(MainWindow) + self.actionAssignFourth.setObjectName(_fromUtf8("actionAssignFourth")) + self.actionAssignFifth = QtWidgets.QAction(MainWindow) + self.actionAssignFifth.setObjectName(_fromUtf8("actionAssignFifth")) + self.actionReviewerChoice = QtWidgets.QAction(MainWindow) self.actionReviewerChoice.setCheckable(True) self.actionReviewerChoice.setChecked(True) self.actionReviewerChoice.setWhatsThis(_fromUtf8("")) self.actionReviewerChoice.setObjectName(_fromUtf8("actionReviewerChoice")) - self.actionAbout = QtGui.QAction(MainWindow) + self.actionAbout = QtWidgets.QAction(MainWindow) self.actionAbout.setObjectName(_fromUtf8("actionAbout")) - self.actionWorkflow = QtGui.QAction(MainWindow) + self.actionWorkflow = QtWidgets.QAction(MainWindow) self.actionWorkflow.setObjectName(_fromUtf8("actionWorkflow")) - self.actionColorize = QtGui.QAction(MainWindow) + self.actionColorize = QtWidgets.QAction(MainWindow) self.actionColorize.setCheckable(True) self.actionColorize.setChecked(True) self.actionColorize.setObjectName(_fromUtf8("actionColorize")) - self.actionAbstractLimit = QtGui.QAction(MainWindow) + self.actionAbstractLimit = QtWidgets.QAction(MainWindow) self.actionAbstractLimit.setObjectName(_fromUtf8("actionAbstractLimit")) + self.actionAbstractLowerLimit = QtWidgets.QAction(MainWindow) + self.actionAbstractLowerLimit.setObjectName(_fromUtf8("actionAbstractLowerLimit")) + self.actionReviewerLimit = QtWidgets.QAction(MainWindow) + self.actionReviewerLimit.setObjectName(_fromUtf8("actionReviewerLimit")) + self.actionReviewerUpperLimit = QtWidgets.QAction(MainWindow) + self.actionReviewerUpperLimit.setObjectName(_fromUtf8("actionReviewerUpperLimit")) self.menuFile.addAction(self.actionOpenSpreadsheet) self.menuFile.addAction(self.actionOpenSession) self.menuFile.addAction(self.actionSaveSession) self.menuFile.addAction(self.actionExport) self.menuFile.addSeparator() self.menuFile.addAction(self.actionQuit) + self.menuAssign.addAction(self.actionAssign) + self.menuAssign.addAction(self.actionClearAssignments) + self.menuAssign.addSeparator() + self.menuAssign.addAction(self.actionAssignAllReviewers) + self.menuAssign.addAction(self.actionAssignReserves) + self.menuAssign.addSeparator() + self.menuAssign.addAction(self.actionAssignAllLabels) + self.menuAssign.addAction(self.actionAssignAllReviewCategories) + self.menuAssign.addAction(self.actionAssignAllSubmissionCategories) + self.menuAssign.addSeparator() + self.menuAssign.addAction(self.actionAssignFirst) + self.menuAssign.addAction(self.actionAssignSecond) + self.menuAssign.addAction(self.actionAssignThird) + self.menuAssign.addAction(self.actionAssignFourth) + self.menuAssign.addAction(self.actionAssignFifth) + self.menuSorting.addAction(self.actionReviewerLimit) + self.menuSorting.addAction(self.actionReviewerUpperLimit) + self.menuSorting.addAction(self.actionAbstractLowerLimit) + self.menuSorting.addAction(self.actionAbstractLimit) + self.menuSorting.addSeparator() self.menuSorting.addAction(self.actionReviewerChoice) self.menuSorting.addAction(self.actionColorize) - self.menuSorting.addAction(self.actionAbstractLimit) self.menuHelp.addAction(self.actionAbout) self.menuHelp.addSeparator() self.menuHelp.addAction(self.actionWorkflow) self.menubar.addAction(self.menuFile.menuAction()) self.menubar.addAction(self.menuSorting.menuAction()) + self.menubar.addAction(self.menuAssign.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.setStatusTip(QtGui.QApplication.translate("MainWindow", "select a category to assign reviewers", None, QtGui.QApplication.UnicodeUTF8)) + MainWindow.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "MainWindow", None )) + self.catlist.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "select a category to assign reviewers", None )) self.catlist.setSortingEnabled(True) - self.catlist.headerItem().setText(0, QtGui.QApplication.translate("MainWindow", "Category #", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.headerItem().setText(1, QtGui.QApplication.translate("MainWindow", "Category Title", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.headerItem().setText(2, QtGui.QApplication.translate("MainWindow", "# of Abstracts", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.headerItem().setText(3, QtGui.QApplication.translate("MainWindow", "# of Assigned Reviewers", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.headerItem().setText(4, QtGui.QApplication.translate("MainWindow", "Pool Size", None, QtGui.QApplication.UnicodeUTF8)) - self.catlist.headerItem().setText(5, QtGui.QApplication.translate("MainWindow", "Assigned Reviewers", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.setStatusTip(QtGui.QApplication.translate("MainWindow", "check reviewers to assign them to the selected category", None, QtGui.QApplication.UnicodeUTF8)) + self.catlist.headerItem().setText(0, QtWidgets.QApplication.translate("MainWindow", "Category #", None )) + self.catlist.headerItem().setText(1, QtWidgets.QApplication.translate("MainWindow", "Category Title", None )) + self.catlist.headerItem().setText(2, QtWidgets.QApplication.translate("MainWindow", "# of Abstracts", None )) + self.catlist.headerItem().setText(3, QtWidgets.QApplication.translate("MainWindow", "# of Assigned Reviewers", None )) + self.catlist.headerItem().setText(4, QtWidgets.QApplication.translate("MainWindow", "Pool Size", None )) + self.catlist.headerItem().setText(5, QtWidgets.QApplication.translate("MainWindow", "Assigned Reviewers", None )) + self.revlist.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "check reviewers to assign them to the selected category", None )) self.revlist.setSortingEnabled(True) - self.revlist.headerItem().setText(0, QtGui.QApplication.translate("MainWindow", "Member #", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(1, QtGui.QApplication.translate("MainWindow", "Type", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(2, QtGui.QApplication.translate("MainWindow", "First", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(3, QtGui.QApplication.translate("MainWindow", "Last", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(4, QtGui.QApplication.translate("MainWindow", "Designation", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(5, QtGui.QApplication.translate("MainWindow", "Institution", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(6, QtGui.QApplication.translate("MainWindow", "Email", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(7, QtGui.QApplication.translate("MainWindow", "Primary Training", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(8, QtGui.QApplication.translate("MainWindow", "Pubmed", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(9, QtGui.QApplication.translate("MainWindow", "Pubmed #", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(10, QtGui.QApplication.translate("MainWindow", "Journal Articles", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(11, QtGui.QApplication.translate("MainWindow", "Reviewed Previously", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(12, QtGui.QApplication.translate("MainWindow", "Choice 1", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(13, QtGui.QApplication.translate("MainWindow", "Choice 2", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(14, QtGui.QApplication.translate("MainWindow", "Choice 3", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(15, QtGui.QApplication.translate("MainWindow", "Choice 4", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(16, QtGui.QApplication.translate("MainWindow", "Choice 5", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(17, QtGui.QApplication.translate("MainWindow", "# of Assigned Abstracts", None, QtGui.QApplication.UnicodeUTF8)) - self.revlist.headerItem().setText(18, QtGui.QApplication.translate("MainWindow", "Assigned Categories", None, QtGui.QApplication.UnicodeUTF8)) - self.menuFile.setTitle(QtGui.QApplication.translate("MainWindow", "File", None, QtGui.QApplication.UnicodeUTF8)) - self.menuSorting.setTitle(QtGui.QApplication.translate("MainWindow", "Sorting", None, QtGui.QApplication.UnicodeUTF8)) - self.menuHelp.setTitle(QtGui.QApplication.translate("MainWindow", "Help", None, QtGui.QApplication.UnicodeUTF8)) - self.actionOpenSpreadsheet.setText(QtGui.QApplication.translate("MainWindow", "Open Spreadsheet (.xls)", None, QtGui.QApplication.UnicodeUTF8)) - self.actionOpenSession.setText(QtGui.QApplication.translate("MainWindow", "Open Session (.mpc)", None, QtGui.QApplication.UnicodeUTF8)) - self.actionSaveSession.setText(QtGui.QApplication.translate("MainWindow", "Save Session (.mpc)", None, QtGui.QApplication.UnicodeUTF8)) - self.actionExport.setText(QtGui.QApplication.translate("MainWindow", "Export (.xls)", None, QtGui.QApplication.UnicodeUTF8)) - self.actionQuit.setText(QtGui.QApplication.translate("MainWindow", "Quit", None, QtGui.QApplication.UnicodeUTF8)) - self.actionReviewerChoice.setText(QtGui.QApplication.translate("MainWindow", "Reviewer\'s Choice", None, QtGui.QApplication.UnicodeUTF8)) - self.actionReviewerChoice.setToolTip(QtGui.QApplication.translate("MainWindow", "Reviewer\'s Choice", None, QtGui.QApplication.UnicodeUTF8)) - self.actionReviewerChoice.setStatusTip(QtGui.QApplication.translate("MainWindow", "sort based off of the reviwer\'s top 5 category choices", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAbout.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) - self.actionWorkflow.setText(QtGui.QApplication.translate("MainWindow", "Typical Workflow", None, QtGui.QApplication.UnicodeUTF8)) - self.actionColorize.setText(QtGui.QApplication.translate("MainWindow", "Colorize Rev. Choices", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAbstractLimit.setText(QtGui.QApplication.translate("MainWindow", "Rev. Abstract Limit", None, QtGui.QApplication.UnicodeUTF8)) + self.revlist.headerItem().setText(0, QtWidgets.QApplication.translate("MainWindow", "Member #", None )) + self.revlist.headerItem().setText(1, QtWidgets.QApplication.translate("MainWindow", "Type", None )) + self.revlist.headerItem().setText(2, QtWidgets.QApplication.translate("MainWindow", "First", None )) + self.revlist.headerItem().setText(3, QtWidgets.QApplication.translate("MainWindow", "Last", None )) + self.revlist.headerItem().setText(4, QtWidgets.QApplication.translate("MainWindow", "Designation", None )) + self.revlist.headerItem().setText(5, QtWidgets.QApplication.translate("MainWindow", "Institution", None )) + self.revlist.headerItem().setText(6, QtWidgets.QApplication.translate("MainWindow", "Email", None )) + self.revlist.headerItem().setText(7, QtWidgets.QApplication.translate("MainWindow", "Primary Training", None )) + self.revlist.headerItem().setText(8, QtWidgets.QApplication.translate("MainWindow", "Pubmed", None )) + self.revlist.headerItem().setText(9, QtWidgets.QApplication.translate("MainWindow", "Pubmed #", None )) + self.revlist.headerItem().setText(10, QtWidgets.QApplication.translate("MainWindow", "Journal Articles", None )) + self.revlist.headerItem().setText(11, QtWidgets.QApplication.translate("MainWindow", "Reviewed Previously", None )) + self.revlist.headerItem().setText(12, QtWidgets.QApplication.translate("MainWindow", "Choice 1", None )) + self.revlist.headerItem().setText(13, QtWidgets.QApplication.translate("MainWindow", "Choice 2", None )) + self.revlist.headerItem().setText(14, QtWidgets.QApplication.translate("MainWindow", "Choice 3", None )) + self.revlist.headerItem().setText(15, QtWidgets.QApplication.translate("MainWindow", "Choice 4", None )) + self.revlist.headerItem().setText(16, QtWidgets.QApplication.translate("MainWindow", "Choice 5", None )) + self.revlist.headerItem().setText(17, QtWidgets.QApplication.translate("MainWindow", "# of Assigned Abstracts", None )) + self.revlist.headerItem().setText(18, QtWidgets.QApplication.translate("MainWindow", "Assigned Categories", None )) + self.menuFile.setTitle(QtWidgets.QApplication.translate("MainWindow", "File", None )) + self.menuAssign.setTitle(QtWidgets.QApplication.translate("MainWindow", "Assign", None )) + self.menuSorting.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings", None )) + self.menuHelp.setTitle(QtWidgets.QApplication.translate("MainWindow", "Help", None )) + self.actionOpenSpreadsheet.setText(QtWidgets.QApplication.translate("MainWindow", "Open Spreadsheet (.xls)", None )) + self.actionOpenSession.setText(QtWidgets.QApplication.translate("MainWindow", "Open Session (.mpc)", None )) + self.actionSaveSession.setText(QtWidgets.QApplication.translate("MainWindow", "Save Session (.mpc)", None )) + self.actionExport.setText(QtWidgets.QApplication.translate("MainWindow", "Export (.xls)", None )) + self.actionQuit.setText(QtWidgets.QApplication.translate("MainWindow", "Quit", None )) + self.actionAssign.setText(QtWidgets.QApplication.translate("MainWindow", "Assign", None )) + self.actionClearAssignments.setText(QtWidgets.QApplication.translate("MainWindow", "Clear assignments", None )) + self.actionAssignAllReviewers.setText(QtWidgets.QApplication.translate("MainWindow", "Assign all reviewers", None )) + self.actionAssignReserves.setText(QtWidgets.QApplication.translate("MainWindow", "Assign reserves", None )) + self.actionAssignAllLabels.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by label", None )) + self.actionAssignAllReviewCategories.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by review category", None )) + self.actionAssignAllSubmissionCategories.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by submission category", None )) + self.actionAssignFirst.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by first label", None )) + self.actionAssignSecond.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by second label", None )) + self.actionAssignThird.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by third label", None )) + self.actionAssignFourth.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by fourth label", None )) + self.actionAssignFifth.setText(QtWidgets.QApplication.translate("MainWindow", "Assign by fifth label", None )) + self.actionReviewerChoice.setText(QtWidgets.QApplication.translate("MainWindow", "Reviewer\'s Choice", None )) + self.actionReviewerChoice.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Reviewer\'s Choice", None )) + self.actionReviewerChoice.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "sort based off of the reviwer\'s top 5 category choices", None )) + self.actionAbout.setText(QtWidgets.QApplication.translate("MainWindow", "About", None )) + self.actionWorkflow.setText(QtWidgets.QApplication.translate("MainWindow", "Typical Workflow", None )) + self.actionColorize.setText(QtWidgets.QApplication.translate("MainWindow", "Colorize Reviewer Choices", None )) + self.actionAbstractLimit.setText(QtWidgets.QApplication.translate("MainWindow", "Maximum abstracts per reviewer", None )) + self.actionAbstractLowerLimit.setText(QtWidgets.QApplication.translate("MainWindow", "Minimum abstracts per reviewer", None )) + self.actionReviewerLimit.setText(QtWidgets.QApplication.translate("MainWindow", "Minimum reviewers per abstract", None )) + self.actionReviewerUpperLimit.setText(QtWidgets.QApplication.translate("MainWindow", "Maximum reviewers per abstract", None )) if __name__ == "__main__": import sys - app = QtGui.QApplication(sys.argv) - MainWindow = QtGui.QMainWindow() + app = QtWidgets.QApplication(sys.argv) + MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show()