class PreviewWidget(QScrollArea): def __init__(self, parent=None): super(PreviewWidget, self).__init__(parent) self.setWidgetResizable(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.widget = QWidget() self.layout = QHBoxLayout() self.layout.addStretch(1) self.layout.setContentsMargins(0, 0, 0, 0) self.widget.setLayout(self.layout) self.setWidget(self.widget) def removeLast(self): item = self.layout.takeAt(0) if item: item.widget().deleteLater() def resizeEvent(self, event): self.widget.setFixedHeight(self.viewport().height()) super(PreviewWidget, self).resizeEvent(event) def addPixmap(self, pixmap): label = ImageLabel(pixmap, self) self.layout.insertWidget(0, label)
class TabLayout(QWidget): def __init__(self, toplabel): super().__init__() ## Get label from ctor args self.label = QLabel(toplabel) ## Create and bind spinbox to update function self.spinbox = QSpinBox() self.spinbox.setMinimum(1) self.spinbox.valueChanged.connect(self.update) ## Keep track of how many elements we have in sublayout 2 self.counter = 1 ## Layouts self.toplayout = QVBoxLayout() self.sublayout1 = QHBoxLayout() self.sublayout2 = QHBoxLayout() ## Build the first line with label and spinbox self.sublayout1.addWidget(self.label) self.sublayout1.addWidget(self.spinbox) ## Build the entity container self.sublayout2.addWidget( EntityData() ) ## Build top level layout self.toplayout.addLayout(self.sublayout1) self.toplayout.addLayout(self.sublayout2) ## Display self.setLayout(self.toplayout) def update(self): if self.spinbox.value() > self.counter: for i in range( 0, self.spinbox.value() - self.counter ): self.sublayout2.addWidget( EntityData() ) elif self.spinbox.value() < self.counter: # Delete all widgets while self.sublayout2.count() > self.spinbox.value(): item = self.sublayout2.takeAt( self.sublayout2.count() - 1 ) if not item: continue w = item.widget() if w: w.setParent(None) # Recreate all widgets #for i in range( 0, self.spinbox.value() ): # self.sublayout2.addWidget( EntityData() ) self.sublayout2.update() self.toplayout.update() self.counter = self.spinbox.value()
class MainWindow(QWidget): ## # \brief Initialization Function def __init__(self): super(MainWindow, self).__init__() #Default variables self.valid = False #Field to determine if the value is valid self.selectorLayout = None #Layout used for selecting a specific source self.sources = ["none", "text", "file", "database"] self.source = {"type": None} self.dests = ["console", "file"] self.dest = {"type": "console"} self.sourceValue = None self.sourceSchema = None #Determine screen settings geo = self.frameGeometry() self.width = QDesktopWidget().availableGeometry().width() self.height = QDesktopWidget().availableGeometry().height() #Define window par meters self.resize(self.width * .5, self.height * .5) self.setWindowTitle("Aqueti Schema Editor") self.show() #create Layouts in UI self.titleLayout = QHBoxLayout() self.mainLayout = QVBoxLayout() self.sourceLayout = QHBoxLayout() self.destLayout = QHBoxLayout() self.valueLayout = QVBoxLayout() self.buttonLayout = QHBoxLayout() #Create frames self.sourceFrame = QFrame() self.destFrame = QFrame() self.valueFrame = QFrame() self.sourceFrame.setFrameStyle(QFrame.Box) self.valueFrame.setFrameStyle(QFrame.Box) self.destFrame.setFrameStyle(QFrame.Box) self.sourceFrame.setLayout(self.sourceLayout) self.destFrame.setLayout(self.destLayout) self.valueFrame.setLayout(self.valueLayout) self.valueFrame.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) #Create Scoll Area for valueFrame self.valueScrollArea = QScrollArea() self.valueScrollArea.updateGeometry() self.valueScrollArea.setWidget(self.valueFrame) self.valueScrollArea.setWidgetResizable(True) #Create title title = QLabel() title.setText("Aqueti Schema Editor") self.titleLayout.addWidget(title) #Add persistent source items sourceTitle = QLabel() sourceTitle.setText("Source:") self.sourceCombo = QComboBox() self.sourceCombo.addItems(self.sources) self.sourceCombo.currentTextChanged.connect( lambda: self.sourceChangeCallback()) selectSourceButton = QPushButton("Load") self.sourceLayout.addWidget(sourceTitle) self.sourceLayout.addWidget(self.sourceCombo) self.sourceMetaLayout = QHBoxLayout() self.sourceMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize) self.sourceLayout.addLayout(self.sourceMetaLayout) self.sourceLayout.addWidget(selectSourceButton) #Add persistent dest destTitle = QLabel() destTitle.setText("Dest:") self.destCombo = QComboBox() self.destCombo.addItems(self.dests) self.destCombo.currentTextChanged.connect( lambda: self.destChangeCallback()) selectDestButton = QPushButton("Load") self.destLayout.addWidget(destTitle) self.destLayout.addWidget(self.destCombo) self.destMetaLayout = QHBoxLayout() self.destMetaLayout.setSizeConstraint(QHBoxLayout.SetMinimumSize) self.destLayout.addLayout(self.destMetaLayout) self.destLayout.addWidget(selectDestButton) #Add Submit Button self.submitButton = QPushButton("Submit") self.submitButton.clicked.connect(lambda: self.submitCallback()) self.buttonLayout.addWidget(self.submitButton) #Add cancel Button cancelButton = QPushButton("Cancel") cancelButton.clicked.connect(lambda: self.cancelCallback()) self.buttonLayout.addWidget(cancelButton) #Add Layouts and draw self.mainLayout.addLayout(self.titleLayout) self.mainLayout.addWidget(self.sourceFrame) self.mainLayout.addWidget(self.destFrame) # self.mainLayout.addWidget( self.valueFrame ) self.mainLayout.addWidget(self.valueScrollArea) # self.mainLayout.addStretch(1) self.mainLayout.addLayout(self.buttonLayout) self.draw() ## # \brief updates the source Layout def updateSourceLayout(self): #Remove current layout information #Remove all widgets from the current layout while self.sourceMetaLayout.count(): item = self.sourceMetaLayout.takeAt(0) self.sourceMetaLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass #Find what our current source is and set the appropriate index index = 0 for i in range(0, self.sourceCombo.count()): if self.sourceCombo.itemText(i) == self.source["type"]: index = i self.sourceCombo.setCurrentIndex(index) #Add fields based on source type if self.source["type"] == "file": #Add filename fileLabel = QLabel() fileLabel.setText("file: ") try: name = self.source["filename"] except: name = "" self.sourceFilenameBox = QLineEdit() self.sourceFilenameBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.sourceFilenameBox.setText(name) # self.sourceFilenameBox.readOnly = True # self.sourceFilenameBox.sizeHint() # self.sourceFilenameBox.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.sourceMetaLayout.addWidget(fileLabel) self.sourceMetaLayout.addWidget(self.sourceFilenameBox) # #Add a submitSource Button # selectSourceButton = QPushButton("Load") # selectSourceButton.clicked.connect( lambda: self.sourceChangeCallback()) # self.sourceLayout.addWidget(selectSourceButton) # self.sourceLayout.addStretch(1) ## # \brief updates the destination layout # def updateDestLayout(self): #Remove current layout information #Remove all widgets from the current layout while self.destMetaLayout.count(): item = self.destMetaLayout.takeAt(0) self.destMetaLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass """ ############################################# # Layout to select a destination ############################################# destTitle = QLabel() destTitle.setText("Dest:") self.destCombo = QComboBox() self.destCombo.addItems(self.dests) """ #Find what our current dest is and set the appropriate index index = 0 for i in range(0, self.destCombo.count()): if self.destCombo.itemText(i) == self.dest["type"]: index = i self.destCombo.setCurrentIndex(index) self.destCombo.currentTextChanged.connect( lambda: self.destChangeCallback()) # self.destLayout.addWidget(destTitle) # self.destLayout.addWidget(self.destCombo) # self.destLayout.addStretch(1) #### # Fill in details base on dest tpye #### if self.dest["type"] == "console": pass elif self.dest["type"] == "file": fileLabel = QLabel() fileLabel.setText("file: ") try: name = self.dest["filename"] except: name = "" self.fileNameBox = QLineEdit() self.fileNameBox.setText(name) # self.destMetaLayout.addWidget(fileLabel) self.destMetaLayout.addWidget(self.fileNameBox) ## # \brief function that is called when the source is changed # def destChangeCallback(self): print("Changing dest") newType = self.destCombo.itemText(self.destCombo.currentIndex()) print("New Type: " + str(newType)) if newType != self.dest["type"]: self.dest = {} self.dest["type"] = newType if self.dest["type"] == "console": pass elif self.dest["type"] == "file": options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog destName, _ = QFileDialog.getSaveFileName( self, "QFileDialog.getSaveFileName()", "", "All Files (*);;JSON Files (*.json)", options=options) self.dest["filename"] = str(destName) else: print("Unsupported Type") self.draw() ## # \brief Update the value layout def updateValueLayout(self): #Remove all widgets from the current layout while self.valueLayout.count(): item = self.valueLayout.takeAt(0) self.valueLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass #If we have data, let's display it if self.sourceSchema != None: valueTitle = QLabel() valueTitle.setText("Schema") self.schemaWidget = SmartWidget().init("Schema", self.sourceValue, self.sourceSchema, showSchema=False) self.valueLayout.addWidget(self.schemaWidget.frame) #Disable the submit button if we don't have a schema if self.sourceSchema == None: self.submitButton.setEnabled(False) else: self.submitButton.setEnabled(True) self.setLayout(self.mainLayout) ## # \brief redraws all dynamic layouts def draw(self): self.updateDestLayout() self.updateSourceLayout() self.updateValueLayout() ## # \brief callback for when the source type changes # def sourceChangeCallback(self): #SDF Add popup to notify of schema loss #Clear the schema to disable the submit button self.sourceSchema = None self.source["type"] = self.sourceCombo.itemText( self.sourceCombo.currentIndex()) if self.source["type"] == "none": self.sourceSchema = {"bsonType": "object"} #If we are a file read the file contents as the value elif self.source["type"] == "file": options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog sourceName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", "", "All Files (*);;JSON Files (*.json)", options=options) self.source["filename"] = str(sourceName) print("Loading: " + str(self.source["filename"])) with open(self.source["filename"]) as json_file: self.sourceSchema = json.load(json_file) print("Loaded Schema:" + str(json.dumps(self.sourceSchema, indent=4))) self.updateSourceLayout() self.updateValueLayout() ## #\brief callback to get result from SmartWidget # # This function assumes that the schema is done. It will produce a popup # asking where and how to save the data # def submitCallback(self): schema = self.schemaWidget.getSchema() if self.dest["type"] == "console": print() print("Schema: (" + str(time.time()) + ")") print(json.dumps(schema, indent=4)) elif self.dest["type"] == "file": print("Writing to: " + str(self.dest["filename"])) with open(self.dest["filename"], 'w') as outfile: json.dump(schema, outfile, indent=4) else: print("Source type: " + str(self.dest["type"]) + " is not currently supported") self.close() #Use save pop-up to save data #self.saveWindow = SaveDataWindow(self.source, schema, self.saveCallback ) print(str(time.time()) + "- schema:") print(str(schema)) ## # \brief Function called after data is saved # def saveCallback(self, success): print("Data Result: " + str(success)) ## # \brief Cancels the change and exits # def cancelCallback(self): print("Exited. No changes were saved") sys.exit(1)
class E5TextEditSearchWidget(QWidget): """ Class implementing a horizontal search widget for QTextEdit. """ def __init__(self, parent=None, widthForHeight=True): """ Constructor @param parent reference to the parent widget @type QWidget @param widthForHeight flag indicating to prefer width for height. If this parameter is False, some widgets are shown in a third line. @type bool """ super(E5TextEditSearchWidget, self).__init__(parent) self.__setupUi(widthForHeight) self.__textedit = None self.__texteditType = "" self.__findBackwards = True self.__defaultBaseColor = ( self.findtextCombo.lineEdit().palette().color(QPalette.Base) ) self.__defaultTextColor = ( self.findtextCombo.lineEdit().palette().color(QPalette.Text) ) self.findHistory = [] self.findtextCombo.setCompleter(None) self.findtextCombo.lineEdit().returnPressed.connect( self.__findByReturnPressed) self.__setSearchButtons(False) self.infoLabel.hide() self.setFocusProxy(self.findtextCombo) def __setupUi(self, widthForHeight): """ Private method to generate the UI. @param widthForHeight flag indicating to prefer width for height @type bool """ self.setObjectName("E5TextEditSearchWidget") self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) # row 1 of widgets self.horizontalLayout1 = QHBoxLayout() self.horizontalLayout1.setObjectName("horizontalLayout1") self.label = QLabel(self) self.label.setObjectName("label") self.label.setText(self.tr("Find:")) self.horizontalLayout1.addWidget(self.label) self.findtextCombo = E5ClearableComboBox(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.findtextCombo.sizePolicy().hasHeightForWidth()) self.findtextCombo.setSizePolicy(sizePolicy) self.findtextCombo.setMinimumSize(QSize(100, 0)) self.findtextCombo.setEditable(True) self.findtextCombo.setInsertPolicy(QComboBox.InsertAtTop) self.findtextCombo.setDuplicatesEnabled(False) self.findtextCombo.setObjectName("findtextCombo") self.horizontalLayout1.addWidget(self.findtextCombo) # row 2 (maybe) of widgets self.horizontalLayout2 = QHBoxLayout() self.horizontalLayout2.setObjectName("horizontalLayout2") self.caseCheckBox = QCheckBox(self) self.caseCheckBox.setObjectName("caseCheckBox") self.caseCheckBox.setText(self.tr("Match case")) self.horizontalLayout2.addWidget(self.caseCheckBox) self.wordCheckBox = QCheckBox(self) self.wordCheckBox.setObjectName("wordCheckBox") self.wordCheckBox.setText(self.tr("Whole word")) self.horizontalLayout2.addWidget(self.wordCheckBox) # layout for the navigation buttons self.horizontalLayout3 = QHBoxLayout() self.horizontalLayout3.setSpacing(0) self.horizontalLayout3.setObjectName("horizontalLayout3") self.findPrevButton = QToolButton(self) self.findPrevButton.setObjectName("findPrevButton") self.findPrevButton.setToolTip(self.tr( "Press to find the previous occurrence")) self.findPrevButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png")) self.horizontalLayout3.addWidget(self.findPrevButton) self.findNextButton = QToolButton(self) self.findNextButton.setObjectName("findNextButton") self.findNextButton.setToolTip(self.tr( "Press to find the next occurrence")) self.findNextButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png")) self.horizontalLayout3.addWidget(self.findNextButton) self.horizontalLayout2.addLayout(self.horizontalLayout3) # info label (in row 2 or 3) self.infoLabel = QLabel(self) self.infoLabel.setText("") self.infoLabel.setObjectName("infoLabel") # place everything together self.verticalLayout.addLayout(self.horizontalLayout1) self.__addWidthForHeightLayout(widthForHeight) self.verticalLayout.addWidget(self.infoLabel) QMetaObject.connectSlotsByName(self) self.setTabOrder(self.findtextCombo, self.caseCheckBox) self.setTabOrder(self.caseCheckBox, self.wordCheckBox) self.setTabOrder(self.wordCheckBox, self.findPrevButton) self.setTabOrder(self.findPrevButton, self.findNextButton) def setWidthForHeight(self, widthForHeight): """ Public method to set the 'width for height'. @param widthForHeight flag indicating to prefer width @type bool """ if self.__widthForHeight: self.horizontalLayout1.takeAt(self.__widthForHeightLayoutIndex) else: self.verticalLayout.takeAt(self.__widthForHeightLayoutIndex) self.__addWidthForHeightLayout(widthForHeight) def __addWidthForHeightLayout(self, widthForHeight): """ Private method to set the middle part of the layout. @param widthForHeight flag indicating to prefer width @type bool """ if widthForHeight: self.horizontalLayout1.addLayout(self.horizontalLayout2) self.__widthForHeightLayoutIndex = 2 else: self.verticalLayout.insertLayout(1, self.horizontalLayout2) self.__widthForHeightLayoutIndex = 1 self.__widthForHeight = widthForHeight def attachTextEdit(self, textedit, editType="QTextEdit"): """ Public method to attach a QTextEdit widget. @param textedit reference to the edit widget to be attached @type QTextEdit, QWebEngineView or QWebView @param editType type of the attached edit widget @type str (one of "QTextEdit", "QWebEngineView" or "QWebView") """ assert editType in ["QTextEdit", "QWebEngineView", "QWebView"] self.__textedit = textedit self.__texteditType = editType self.wordCheckBox.setVisible(editType == "QTextEdit") def keyPressEvent(self, event): """ Protected slot to handle key press events. @param event reference to the key press event (QKeyEvent) """ if self.__textedit and event.key() == Qt.Key_Escape: self.__textedit.setFocus(Qt.ActiveWindowFocusReason) event.accept() @pyqtSlot(str) def on_findtextCombo_editTextChanged(self, txt): """ Private slot to enable/disable the find buttons. @param txt text of the combobox (string) """ self.__setSearchButtons(txt != "") self.infoLabel.hide() self.__setFindtextComboBackground(False) def __setSearchButtons(self, enabled): """ Private slot to set the state of the search buttons. @param enabled flag indicating the state (boolean) """ self.findPrevButton.setEnabled(enabled) self.findNextButton.setEnabled(enabled) def __findByReturnPressed(self): """ Private slot to handle the returnPressed signal of the findtext combobox. """ self.__find(self.__findBackwards) @pyqtSlot() def on_findPrevButton_clicked(self): """ Private slot to find the previous occurrence. """ self.__find(True) @pyqtSlot() def on_findNextButton_clicked(self): """ Private slot to find the next occurrence. """ self.__find(False) def __find(self, backwards): """ Private method to search the associated text edit. @param backwards flag indicating a backwards search (boolean) """ if not self.__textedit: return self.infoLabel.clear() self.infoLabel.hide() self.__setFindtextComboBackground(False) txt = self.findtextCombo.currentText() if not txt: return self.__findBackwards = backwards # This moves any previous occurrence of this statement to the head # of the list and updates the combobox if txt in self.findHistory: self.findHistory.remove(txt) self.findHistory.insert(0, txt) self.findtextCombo.clear() self.findtextCombo.addItems(self.findHistory) if self.__texteditType == "QTextEdit": ok = self.__findPrevNextQTextEdit(backwards) self.__findNextPrevCallback(ok) elif self.__texteditType == "QWebEngineView": self.__findPrevNextQWebEngineView(backwards) def __findPrevNextQTextEdit(self, backwards): """ Private method to to search the associated edit widget of type QTextEdit. @param backwards flag indicating a backwards search @type bool @return flag indicating the search result @rtype bool """ if backwards: flags = QTextDocument.FindFlags(QTextDocument.FindBackward) else: flags = QTextDocument.FindFlags() if self.caseCheckBox.isChecked(): flags |= QTextDocument.FindCaseSensitively if self.wordCheckBox.isChecked(): flags |= QTextDocument.FindWholeWords ok = self.__textedit.find(self.findtextCombo.currentText(), flags) if not ok: # wrap around once cursor = self.__textedit.textCursor() if backwards: moveOp = QTextCursor.End # move to end of document else: moveOp = QTextCursor.Start # move to start of document cursor.movePosition(moveOp) self.__textedit.setTextCursor(cursor) ok = self.__textedit.find(self.findtextCombo.currentText(), flags) return ok def __findPrevNextQWebEngineView(self, backwards): """ Private method to to search the associated edit widget of type QWebEngineView. @param backwards flag indicating a backwards search @type bool """ from PyQt5.QtWebEngineWidgets import QWebEnginePage findFlags = QWebEnginePage.FindFlags() if self.caseCheckBox.isChecked(): findFlags |= QWebEnginePage.FindCaseSensitively if backwards: findFlags |= QWebEnginePage.FindBackward self.__textedit.findText(self.findtextCombo.currentText(), findFlags, self.__findNextPrevCallback) def __findNextPrevCallback(self, found): """ Private method to process the result of the last search. @param found flag indicating if the last search succeeded @type bool """ if not found: txt = self.findtextCombo.currentText() self.infoLabel.setText( self.tr("'{0}' was not found.").format(txt)) self.infoLabel.show() self.__setFindtextComboBackground(True) def __setFindtextComboBackground(self, error): """ Private slot to change the findtext combo background to indicate errors. @param error flag indicating an error condition (boolean) """ le = self.findtextCombo.lineEdit() p = le.palette() if error: p.setBrush(QPalette.Base, QBrush(QColor("#FF6666"))) p.setBrush(QPalette.Text, QBrush(QColor("#000000"))) else: p.setBrush(QPalette.Base, self.__defaultBaseColor) p.setBrush(QPalette.Text, self.__defaultTextColor) le.setPalette(p) le.update()
class FileWidget(QWidget): """ Represents a file. """ def __init__(self, source_db_object, submission_db_object, controller, file_ready_signal, align="left"): """ Given some text, an indication of alignment ('left' or 'right') and a reference to the controller, make something to display a file. Align is set to left by default because currently SecureDrop can only accept files from sources to journalists. """ super().__init__() self.controller = controller self.source = source_db_object self.submission = submission_db_object self.file_uuid = self.submission.uuid self.align = align self.layout = QHBoxLayout() self.update() self.setLayout(self.layout) file_ready_signal.connect(self._on_file_download) def update(self): icon = QLabel() icon.setPixmap(load_image('file.png')) if self.submission.is_downloaded: description = QLabel("Open") else: human_filesize = humanize_filesize(self.submission.size) description = QLabel("Download ({})".format(human_filesize)) if self.align != "left": # Float right... self.layout.addStretch(5) self.layout.addWidget(icon) self.layout.addWidget(description, 5) if self.align == "left": # Add space on right hand side... self.layout.addStretch(5) def clear(self): while self.layout.count(): child = self.layout.takeAt(0) if child.widget(): child.widget().deleteLater() @pyqtSlot(str) def _on_file_download(self, file_uuid: str) -> None: if file_uuid == self.file_uuid: self.clear() # delete existing icon and label self.update() # draw modified widget def mouseReleaseEvent(self, e): """ Handle a completed click via the program logic. The download state of the file distinguishes which function in the logic layer to call. """ if self.submission.is_downloaded: # Open the already downloaded file. self.controller.on_file_open(self.submission) else: # Download the file. self.controller.on_file_download(self.source, self.submission)
class PadCalc(QWidget): def __init__(self): super().__init__() load_data() card_tags = ['leader','sub1','sub2','sub3','sub4','friend'] self.cards = { t: CardIcon() for t in card_tags } self.vlayout = QVBoxLayout(self) self.vlayout.setSpacing(0) self.setLayout(self.vlayout) self.userbox = QHBoxLayout() userfield = QLineEdit() userbutton = QPushButton('Load') userbutton.clicked.connect(lambda: self.set_user(userfield.text())) self.userbox.addWidget(userfield) self.userbox.addWidget(userbutton) userfield.returnPressed.connect(userbutton.click) self.vlayout.addLayout(self.userbox) maxcheckbox = QCheckBox('Use maxed stats?') maxcheckbox.stateChanged[int].connect(self.setMaxed) self.vlayout.addWidget(maxcheckbox) self.teamchooser = QComboBox(self) self.teamchooser.currentIndexChanged[int].connect(self.set_team) self.vlayout.addWidget(self.teamchooser) teambox = QHBoxLayout() teambox.addStretch(1) for card in card_tags: teambox.addWidget(self.cards[card]) teambox.setSpacing(0) teambox.addStretch(1) teambox.setAlignment(Qt.AlignCenter) self.vlayout.addLayout(teambox) self.board = Board() self.vlayout.addWidget(self.board) self.vlayout.itemAt(self.vlayout.indexOf(self.board)).setAlignment(Qt.AlignCenter) self.orbchooser = QHBoxLayout() b = OrbButton(value = 0) b.clicked.connect(functools.partial(self.setPaintOrb,Orb.Null)) self.orbchooser.addWidget(b) for i in ORBS: b = OrbButton(value=i) #print('Setting click value of button %s to %s' % (id(b),i)) b.clicked.connect(functools.partial(self.setPaintOrb,i)) self.orbchooser.addWidget(b) self.vlayout.addLayout(self.orbchooser) self.damagereadout = QLabel() font = QFont() font.setPointSize(30) self.damagereadout.setAlignment(Qt.AlignCenter) self.damagereadout.setFont(font) self.vlayout.addWidget(self.damagereadout) self.board.valueChanged.connect(self.update_damage) labels = ['atk','combos','leaders','enhance','prongs','rows'] lfont = QFont() lfont.setPointSize(9) vfont = QFont() vfont.setPointSize(12) self.details = {key: QVBoxLayout() for key in labels} for i in labels: label = QLabel(i) label.setFont(lfont) label.setAlignment(Qt.AlignCenter) label.setMargin(0) label.setContentsMargins(0,0,0,0) label.setIndent(0) self.details[i].label = label self.details[i].addWidget(self.details[i].label) value = QLabel('1') value.setFont(vfont) value.setAlignment(Qt.AlignCenter) value.setMargin(0) value.setIndent(0) value.setContentsMargins(0,0,0,0) self.details[i].value = value self.details[i].addWidget(self.details[i].value) self.details[i].setContentsMargins(1,1,1,1) self.detailreadout = QHBoxLayout() for i in labels: self.detailreadout.addLayout(self.details[i]) timeslabel = QLabel('\u00d7') timeslabel.setMargin(0) timeslabel.setIndent(0) timeslabel.setAlignment(Qt.AlignCenter) timeslabel.setContentsMargins(0,0,0,0) self.detailreadout.addWidget(timeslabel) self.detailreadout.takeAt(self.detailreadout.count()-1) self.vlayout.addLayout(self.detailreadout) self.vlayout.addStretch(1000) self.skillbox = QHBoxLayout() self.vlayout.addLayout(self.skillbox) #self.set_user('korora') def setMaxed(self,state): Card.use_max_stats = (state == Qt.Checked) self.drawui() self.update_damage() self.set_team(self.teamchooser.currentIndex()) def setPaintOrb(self,orb): global paintOrb paintOrb = orb def set_user(self,username): newuser = User(username) if hasattr(self,'user'): olduser = self.user.username else: olduser = '' if hasattr(newuser,'teams') and len(newuser.teams) > 0: teamchooser = self.teamchooser self.user = newuser index = teamchooser.currentIndex() try: teamchooser.currentIndexChanged[int].disconnect() except: return teamchooser.clear() for team in self.user.teams: teamchooser.addItem('%s' % (team['name'])) if newuser.username != olduser: self.set_team(0) else: teamchooser.setCurrentIndex(index) self.set_team(index) teamchooser.currentIndexChanged[int].connect(self.set_team) def update_damage(self): (match,enhanced,row) = self.board.match() nmatch = sum(len(v) for v in match.values()) nrow = sum(v for v in row.values()) (dmg, multipliers) = compute_damage((match,enhanced,row),self.team) self.damagereadout.setText('{:,}'.format(round(sum([sum(i) for i in dmg.values()])))) for i in multipliers: if i is not 'atk': self.details[i].value.setText('%.2f' % multipliers[i]) else: self.details[i].value.setText('%d' % multipliers[i]) for card in self.cards.values(): # add a damage label dam = dmg[card.card] card.main_attack = dam[0] card.sub_attack = dam[1] card.repaint() def set_team(self,index): teamdata = self.user.teams[index] team = [] for i in ['leader','sub1','sub2','sub3','sub4']: team += [Card().load_from_id(self.user,teamdata[i])] friend = { 'monster': teamdata['friend_leader'], 'plus_atk': teamdata['friend_atk'], 'plus_hp': teamdata['friend_hp'], 'plus_rcv': teamdata['friend_rcv'], 'current_awakening': teamdata['friend_awakening'], 'lv': teamdata['friend_level'], 'current_skill': teamdata['friend_skill'] } team += [Card().load_from_card(friend)] self.team = Team(team) #print('|'+self.teamchooser.itemText(index)+'|') #print(len(self.teamchooser.itemText(index))) self.teamchooser.setMinimumContentsLength(len(self.teamchooser.itemText(index))-3) for i in range(self.skillbox.count()): w = self.skillbox.takeAt(i) w.widget().deleteLater() #svc = SkillViewController(skill=self.team.lskills[0]) #svc.skillsChanged.connect(self.update_damage) #self.skillbox.addWidget(svc) self.drawui() self.update_damage() def drawui(self): self.cards['leader'].card = (self.team.cards[0]) self.cards['sub1'].card = (self.team.cards[1]) self.cards['sub2'].card = (self.team.cards[2]) self.cards['sub3'].card = (self.team.cards[3]) self.cards['sub4'].card = (self.team.cards[4]) self.cards['friend'].card = (self.team.cards[5]) for card in self.cards.values(): card.load_icon()
class AddNewKey(QFrame): """ QFrame popup widget for adding new element """ def __init__(self, connection: sqlite3.Connection, cursor: sqlite3.Cursor, secret_key: str, data: typing.Union[typing.List, None], parent: object): super().__init__(parent) self.parent = parent self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.connection = connection self.cursor = cursor self.secret_key = secret_key self.data = data self.w = self.parent.width() self.h = self.parent.height() self.setFixedSize(self.w, self.h) self.setObjectName("AddFrame") self.setStyleSheet(qframe_css.add_new_key_style) # Elements current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") close_button = QPushButton() close_button.setObjectName("close") close_button.setFixedSize(QSize(13, 13)) close_button.setCursor(Qt.PointingHandCursor) close_button.setIcon(QIcon(":/quit")) close_button.clicked.connect(self._close) if not self.data: self.combo = QComboBox() self.combo.setObjectName("combo") self.combo.addItems( ("Web Sites", "Web Accounts", "Emails", "Credit Cards", "E-commerce", "Secrets", "Software", "Forums")) self.combo.currentTextChanged.connect(self.generate_child_list) self.child = QLineEdit() self.child.setPlaceholderText("Branch *") self.title = QLineEdit() self.title.setPlaceholderText("Title *") self.name = QLineEdit() self.name.setPlaceholderText("Login") self.email = QLineEdit() self.email.setPlaceholderText("Email") self.password = QLineEdit() self.password.setPlaceholderText("Password *") self.password.setEchoMode(QLineEdit.Password) self.url = QLineEdit() self.url.setPlaceholderText("Url") self.phone = QLineEdit() self.phone.setPlaceholderText("Phone") self.created = QLineEdit() self.created.setPlaceholderText("Create Date *") if not self.data: self.created.setText(current_time) else: self.created.setText(self.data[6]) self.created.setEnabled(False) self.modified = QLineEdit() self.modified.setPlaceholderText("Modified Date *") self.modified.setText(current_time) self.modified.setEnabled(False) if not self.data: for elements in (self.combo, self.child): elements.setFixedSize(400, 40) elements.setObjectName("field") for elements in (self.title, self.name, self.email, self.password, self.url, self.phone, self.created, self.modified): elements.setFixedSize(400, 40) elements.setObjectName("field") save_button = QPushButton() save_button.setObjectName("save") save_button.setText("Сохранить") save_button.setFixedSize(300, 40) save_button.setCursor(Qt.PointingHandCursor) save_button.clicked.connect(self._save) # Animation self.animation = AnimationService(self, b"geometry", 200, QRect(0, -self.h, self.w, self.h), QRect(0, 0, self.w, self.h)).init_animation() self.animation.start() # Timer self.timer = QTimer(self) self.timer.setInterval(210) self.timer.timeout.connect(self._quit) # LAYOUTS if not self.data: self.cc = QHBoxLayout() self.cc.setContentsMargins(0, 6, 0, 4) self.cc.setAlignment(Qt.AlignCenter) layout = QVBoxLayout() layout.addWidget(close_button, alignment=Qt.AlignRight) layout.addSpacing(10) if not self.data: layout.addWidget(self.combo, alignment=Qt.AlignCenter) layout.addLayout(self.cc) layout.addWidget(self.child, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.title, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.name, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.email, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.password, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.url, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.phone, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.created, alignment=Qt.AlignCenter) layout.addSpacing(10) layout.addWidget(self.modified, alignment=Qt.AlignCenter) layout.addSpacing(20) layout.addWidget(save_button, alignment=Qt.AlignCenter) layout.addStretch(1) self.setLayout(layout) self.title.setFocus(True) if not self.data: self.generate_child_list() else: self.title.setText(self.data[0]) self.name.setText(self.data[1]) self.email.setText(self.data[2]) self.url.setText(self.data[4]) self.phone.setText(self.data[5]) def generate_child_list(self): self.sender().blockSignals(True) for pos in reversed(range(self.cc.count())): curr_item = self.cc.takeAt(pos).widget() if curr_item is not None: curr_item.deleteLater() sql = "SELECT DISTINCT child " \ "FROM passwords " \ "WHERE parent=? " \ "ORDER BY child ASC" query = self.cursor.execute(sql, (self.combo.currentText(), )) for item in query.fetchall(): r_elem = QRadioButton() r_elem.setObjectName("child-element") r_elem.setText(item[0]) r_elem.setFixedHeight(20) r_elem.setCursor(Qt.PointingHandCursor) r_elem.clicked.connect(self.child_text_replace) self.cc.addWidget(r_elem) self.sender().blockSignals(False) def child_text_replace(self): self.child.setText(self.sender().text()) def _save(self): crypt_password = run_encode(self.secret_key, self.password.text().encode("utf-8")) if not self.data: sql = "INSERT INTO passwords " \ "(parent, child, title, login, email, password, url, phone, created, modified) " \ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" self.cursor.execute( sql, (self.combo.currentText(), self.child.text(), self.title.text(), self.name.text(), self.email.text(), crypt_password, self.url.text(), self.phone.text(), self.created.text(), self.modified.text())) else: sql = "UPDATE passwords " \ "SET title=?, login=?, email=?, password=?, url=?, phone=?, modified=? " \ "WHERE id=?" self.cursor.execute( sql, (self.title.text(), self.name.text(), self.email.text(), crypt_password, self.url.text(), self.phone.text(), self.modified.text(), self.data[3])) self.connection.commit() self._close() chime.success() def _close(self): self.animation.setStartValue(QRect(0, 0, self.w, self.h)) self.animation.setEndValue(QRect(0, -self.h, self.w, self.h)) self.animation.start() self.timer.start() def _quit(self): self.close() self.deleteLater() self.parent._add = None def keyPressEvent(self, event: QEvent) -> None: """ Track key press """ if event.key() == Qt.Key_Escape: self._close()
class ChurchUI: def __init__(self, window, church, target_layout): self.church = church self.target_layout = target_layout self.window = window self.layout = None self.church_button = None self.del_button = None self.edit_button = None self.church_text = None self.place_text = None self.msg = None self.student_ui = None self.create() def create(self): self.layout = QHBoxLayout() self.layout.setSpacing(2) # Create Church Button self.church_button = Button(self.church.get_name(), width=None, height=40) self.church_button.align_text() # Create church Text self.church_text = TextBox(self.church.name, height=40) # Create Place Text self.place_text = TextBox(self.church.place, height=40) # Create Edit Button self.edit_button = Button('EDIT', width=40, height=40) # Create Del button self.del_button = Button('DEL', width=40, height=40) self.layout.addWidget(self.church_text) self.layout.addWidget(self.place_text) self.layout.addWidget(self.church_button) self.layout.addWidget(self.edit_button) self.layout.addWidget(self.del_button) self.target_layout.insertLayout(self.target_layout.count() - 1, self.layout) self.church_button.clicked.connect(self.select) self.church_text.returnPressed.connect(self.finish_edit) self.place_text.returnPressed.connect(self.finish_edit) self.edit_button.clicked.connect(self.edit) self.del_button.clicked.connect(self.passive_delete) def delete(self): if self.layout is not None: while self.layout.count(): item = self.layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() else: pass self.church.delete() del self def hide(self): pass def select(self): try: if not self.student_ui: self.student_ui = student.Students(self.window, self.church) data.selection.select(self.student_ui) except Exception as e: print(e) def deselect(self): self.window.setCentralWidget(None) def passive_delete(self): self.msg = display.QuestionMessageBox( self.window, 'Delete Church', 'Do You want to delete church ' + self.church.name + ' and all the students?', self.delete) def edit(self): if self.church_button.isVisible(): data.editing.start_edit(self) else: data.editing.finish_edit() def start_edit(self): self.church_button.hide() # self.box_layout self.church_text.show() self.place_text.show() def finish_edit(self): if self.church_text.text() and self.place_text.text(): self.church_text.hide() self.place_text.hide() self.church.name = self.church_text.text() self.church.place = self.place_text.text() self.church_button.setText(self.church.get_name()) self.church_button.show() def revert_edit(self): self.church_text.hide() self.place_text.hide() self.church_button.setText(self.church_text.text() + ', ' + self.place_text.text()) self.church_button.show()
class FigshareObjectWindow(QMdiSubWindow): """ An abstract base class for viewing high level Figshare objects. """ def __init__(self, app: QApplication, OAuth_token: str, parent: QMainWindow): """ Initialise the window Args: app: Main thread application object. OAuth_token: Users Figshare authentication token obtained at login. parent: Reference to the applications main window, where various variables are kept. """ # Super the QMdiSubWindow init function super().__init__() # Create class variables of init args self.app = app self.token = OAuth_token self.parent = parent # Create shortned reference to open windows set in the main window self.open_windows = self.parent.open_windows # Initialise the Figshare information and UI self.initFig() self.initUI() def initFig(self): """ Function should create a class variable with a list of the Figshare objects to be visualised. Should be overwritten by child classes Returns: None """ self.object_list = self.get_object_list() def initUI(self): """ Formats and shows the window. Returns: None """ # Call the window formatting function self.format_window() # Create a horizontal box layout to hold the figshare object buttons self.object_buttons_box = QHBoxLayout() # Create a vertical box layout to hold the project window widgets and layouts self.vbox = QVBoxLayout() # Add the Figshare Object buttons to the vertical box layout init_finish = len(self.object_list) if init_finish > 4: init_finish = 4 self.create_object_bar(0, init_finish) self.vbox.addLayout(self.object_buttons_box) # Add the scroll bar to the vertical box layout self.s_bar = self.scroll_bar() self.vbox.addWidget(self.s_bar) # Create an encompassing layout self.hbox = QHBoxLayout() # Create a layout for the search and managment widgets control_layout = QVBoxLayout() control_layout.addWidget(self.search_bar()) control_layout.addLayout(self.management_buttons()) # Add the control layout and the vertical button layout to the encompassing layout self.hbox.addLayout(control_layout) self.hbox.addLayout(self.vbox) # Create a central widget for the objects window window_widget = QWidget() # Add the vertical box layout window_widget.setLayout(self.hbox) # Set the projects window widget self.setWidget(window_widget) # Window Formatting # ================= def format_window(self): """ Formats the window based on the available space in the primary screen. Returns: None """ # Gets the QRect of the main window geom = self.parent.geometry() # Gets the Qrect of the sections window section_geom = self.parent.section_geom # Define geometries for the projects window x0 = section_geom.x() + section_geom.width() y0 = section_geom.y() w = geom.width() - x0 h = ((geom.height() - y0) / 6) self.setGeometry(x0, y0, w, h) # Remove frame from projects window self.setWindowFlags(Qt.FramelessWindowHint) # Set the default tool tip time duration to 1 second self.tool_tip_time = 1000 # Window Widgets # ============== def scroll_bar(self): """ Creates a QScrollBar set to the size of the figshare objects list. Returns: s_bar (QScrollBar): Scroll bar used to move through the list of Figsahre objects. """ s_bar = QScrollBar(Qt.Horizontal) s_bar.setMaximum(len(self.object_list) - 4) s_bar.sliderMoved.connect(self.slider_val) s_bar.valueChanged.connect(self.slider_val) return s_bar def create_obj_thumb(self, title: str, published_date: str, object_id: int): """ Creates a large QPushButton with information on the objects title, and published date. Args: title: Name of the Figshare object. published_date: String representation of when/if the object was made public. object_id: Figshare object ID number Returns: btn (QPushButton): Button connected to open a subwindow with its specific ID number. """ # Get the scaling rations for the current display w_ratio, f_ratio = scaling_ratio(self.app) # Scale the font sizes title_fnt_size = 12 * f_ratio date_ftn_size = 8 * f_ratio # Create the Title Label # Create the title label title_lbl = QLabel() title_lbl.setText("{}".format(title)) title_lbl_fnt = QFont('SansSerif', title_fnt_size) title_lbl_fnt.setBold(True) title_lbl.setFont(title_lbl_fnt) title_lbl.setWordWrap(True) # Create the date label date_lbl = QLabel() if published_date is None: published_date = 'Private' date_lbl.setText("Published: {}".format(published_date)) date_lbl_fnt = QFont('SansSerif', date_ftn_size) date_lbl.setFont(date_lbl_fnt) date_lbl.setStyleSheet('color: gray') date_lbl.setWordWrap(True) # Create a layout to hold the labels lbl_box = QVBoxLayout() # Add labels to layout lbl_box.addWidget(title_lbl) lbl_box.addWidget(date_lbl) # Create a button for the project btn = QPushButton(self) checkable_button(self.app, btn) btn.setLayout(lbl_box) btn.clicked[bool].connect(lambda: self.on_object_pressed(object_id)) self.object_buttons_box.addWidget(btn) def create_object_bar(self, start: int, finish: int): """ Creates a series of Object Push Buttons from a defined subset of the total list. Args: start: Starting element from the objects list. finish: Finishing element from the objects list. Returns: None """ self.buttons = {} i = 0 for object_pos in range(start, finish): title = self.object_list[object_pos]['title'] pub_date = self.object_list[object_pos]['published_date'] object_id = self.object_list[object_pos]['id'] self.create_obj_thumb(title, pub_date, object_id) self.buttons[object_id] = self.object_buttons_box.itemAt( i).widget() i += 1 def management_buttons(self): """ Creates a QLayout object that hold the buttons used for creating and deleting Figsahre objects. Returns: hbox (QHBoxLayout): Horizontal layout with the create and delete buttons within it. """ # Create New Project Button np_btn = QPushButton() np_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) np_btn.setIcon( QIcon(os.path.abspath(__file__ + '/../..' + '/img/Folder-48.png'))) np_btn.setToolTip('Create a new Figshare Object') np_btn.setToolTipDuration(self.tool_tip_time) np_btn.pressed.connect(self.on_create_btn_pressed) # Create Delete Project Button del_btn = QPushButton() del_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) del_btn.setIcon( QIcon(os.path.abspath(__file__ + '/../..' + '/img/del_folder.png'))) del_btn.setToolTip('Delete Selected Object') del_btn.setToolTipDuration(self.tool_tip_time) del_btn.pressed.connect(self.on_delete_btn_pressed) # Create layout to hold buttons hbox = QHBoxLayout() # Add Buttons to layout hbox.addWidget(np_btn) hbox.addWidget(del_btn) return hbox def search_bar(self): """ Creates a QLineEdit object for the user to enter search queries that will filter the total object list. Returns: edit (QLineEdit): Edit field connected to perform a search either when return is pressed or focus is shifted away from the edit. """ # Create text box edit = QLineEdit() # Set font style search_bar(self.app, edit) # Set place holder text edit.setPlaceholderText('Search') # Add a clear button to the line edit edit.setClearButtonEnabled(True) # Add mouse over text edit.setToolTip('Search for specific Figshare objects') edit.setToolTipDuration(self.tool_tip_time) # Connect search function to the return key edit.returnPressed.connect(lambda: self.search_on_return(edit.text())) edit.textChanged.connect(lambda: self.search_on_clear(edit.text())) return edit # Widget Actions # ============== def slider_val(self): """ Called when the objects slider is changed. Removes all existing buttons and regenerates from the new list position. Returns: None """ # Remove all existing button widgets while self.object_buttons_box.count(): item = self.object_buttons_box.takeAt(0) item.widget().deleteLater() # Get the current value of the scroll bar s_bar_pos = self.s_bar.value() # Define how many buttons to visualise if 1 < len(self.object_list) < 4: number = len(self.object_list) else: number = 4 self.s_bar.setMaximum( len(self.object_list) - number) # Will be zero if less than 4 items in list self.create_object_bar( s_bar_pos, s_bar_pos + number) # Recreates the button view from the new position def search_init(self): """ Called when the object search bar is used. Removes all existing buttons anfe regenerates from the new list. Returns: None """ # Remove all existing button widgets while self.object_buttons_box.count(): item = self.object_buttons_box.takeAt(0) item.widget().deleteLater() # Define how many buttons to visualise if 1 <= len(self.object_list) <= 4: number = len(self.object_list) else: number = 4 self.s_bar.setMaximum( len(self.object_list) - number) # Will be zero if less than 4 items in list self.create_object_bar( 0, number) # Recreates the button view from the new position def search_on_return(self, search_text: str): """ Called when the return key is pressed in the search bar. Will search the relavant Figshare object endpoint based on the query string. Will overwrite the existing objects list with that of the search result. Args: search_text: Elastic search query with which to search the object titles for. Returns: None """ self.object_list = self.search_objects( search_text) # Perform the search self.search_init() # Redraw the object buttons def search_on_clear(self, search_text: str): """ Called when the search bar is cleared, or if the focus is removed and the search string is empty Args: search_text: strign from the search LineEdit Returns: None """ if search_text == '': self.object_list = self.get_object_list() self.slider_val() def on_create_btn_pressed(self): """ Called when the create new object button is pressed. MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. if new_''_window in self.open_Windows: self.open_windows.remove(new_''_window) self.parent.new_''_window.close() else: self.open_windows.remove(''_window) self.close() self.open_windows.add('new_''_window) self.parent.new_''_window = New''Window(self.app, self.token, self.parent) self.parent.mdi.addSubWindow(self.parent.new_''_window) self.parent.new_''_window.show() Returns: None """ pass def is_info_open(self): """ Called to see if there is a Figshare object info window open. MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. if ''_info_window in self.open_windows: open_obj_id = self.parent.''_info_window.''_id return True, open_obj_id else: return False, None Returns: open (bool): True, or False dependent on if info window is already open object_id (int): Figshare object ID number """ pass def close_object_info_window(self): """ Called when the existing object info window needs to be closed. MUST BE OVERWRITTEN BY CHIDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specifc figshare object being used. self.open_windows.remove("''_info_window") self.parent.''_info_window.close() if ''_articles_window in self.open_windows: self.open_windows.remove("''_articles_window") self.parent.''_articles_window.close() if 'article_edit_window' in self.open_windows: self.open_windows.remove('article_edit_window') self.parent.article_edit_window.close() Returns: None """ def create_new_object_info_window(self, object_id: int): """ Called when a new object info window is to be created. MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. self.open_windows.add("''_info_window") self.parent.''_info_window = ''InfoWindow(self.app, self.token, self.parent, self.object_id) self.parent.mdi.addSubWindow(self.parent.''_info_window) self.parent.''_info_window.show() Args: object_id: Figshare object ID number. Returns: None """ pass def on_object_pressed(self, object_id: int): """ Called when an object button is clicked. If an object info window is already open will see if it is the same as the object just pressed. If it is, then it is closed. If it is not the same then the currently open info window is closed and the new object window is opened in its place. Args: object_id: Figshare object ID number. Returns: None """ # Check to see if an object window is open info_open, open_obj_id = self.is_info_open() if info_open: # Check to see if the open object is the same as the object that was just pressed if open_obj_id != object_id: # Check to see if we need to toggle a button by seeing if the object button still exists. # It may have been scrolled away from. if open_obj_id in self.buttons: # Finally check that it is checked, and un-check it if so. if self.buttons[open_obj_id].isChecked(): self.buttons[open_obj_id].toggle() # Close the currently open info window self.close_object_info_window() # Create and open new object info window self.create_new_object_info_window(object_id) # If the button pressed corresponds to the existing object else: # Close the object info window self.close_object_info_window() else: self.create_new_object_info_window(object_id) def on_delete_btn_pressed(self): """ Called when the object delete button is pressed. Returns: None """ # See if an info window is open, and get its object ID number if so info_open, open_obj_id = self.is_info_open() if info_open: # Create a confirmation dialog msg = "Are you sure you want to delete the open Figshare Object?" msg_box = QMessageBox.question(self, "Deletion Confirmation", msg, QMessageBox.Yes, QMessageBox.No) if msg_box == QMessageBox.Yes: # Attempt to delete the Figshare object successful = self.delete_object(open_obj_id) if successful: con_reply = QMessageBox.information( self, "Deletion Confirmation", "Object successfully deleted.", QMessageBox.Ok) if con_reply is not None: self.reopen_objects() else: con_reply = QMessageBox.warning( self, "Deletion Confirmation", "Object could not be deleted", QMessageBox.Ok) if con_reply is not None: self.reopen_objects() def reopen_objects(self): """ Called to open and close the figshare objects window MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. for i in range(2): self.parent.section_window.on_''_btn_pressed() Returns: None """ # Figshare API Interface Functions # ================================ def get_object_list(self): """ Called to return a list of Figshare object associated to the user. MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. '' = ''(self.token) object_list = ''.get_list() return object_list Returns: object_list (list of dicts): List of users Figshare objects. """ pass def search_objects(self, search_text: str): """ Gets a list of objects matching the users search query. MUST BE OVERWRITTEN BY CHILDREN. Examples: Example of code is given where '' denotes the sections that should be manually defined to the specific figshare object being used. '' = ''(self.token) result = ''.search(search_text) if len(result) == 0: result = ''.get_list() return result Args: search_text: Figshare style elastic search string Returns: result (list of dicts): Gives a list of dictionary objects that either match those of the search criteria, or returns the full set if no matches found. """ pass def delete_object(self, object_id: int): """
class App(QMainWindow): game_filter_positions = { "nhl": ['C', 'LW', 'RW', 'D'], "nfl": ['QB', 'RB', 'WR', 'TE', 'K', 'DEF'] } game_projection_columns = { "nhl": { 'name': 'Name', 'posn_display': 'POS', 'team': 'Team', 'csh_rank': 'CSH', 'preseason_rank': 'Preseason', 'current_rank': 'Current', 'GP': 'GP', 'fantasy_score': 'Score' }, "nfl": { 'name': 'Name', 'posn_display': 'POS', 'team': 'Team', 'Bye': 'Bye', 'fan_points': 'fan_points', 'overall_rank': 'Rank', 'fp_rank': 'FP_rank', 'position_rank': 'FP_Pos' } } # ['name', 'position', 'player_id', 'GP', 'Bye', 'fan_points', # 'overall_rank', 'percent_rostered', 'pass_yds', 'pass_td', 'pass_int', # 'pass_sack', 'rush_attempts', 'rush_yards', 'rush_tds', # 'receiving_targets', 'receiving_receptions', 'receiving_yards', # 'receiving_tds', 'team'] def __init__(self): super().__init__() self.title = "Yahoo Draft Board" self.left = 10 self.top = 10 self.width = 3200 self.height = 1000 self.main_widget = QWidget(self) self.oauth = OAuth2(None, None, from_file='oauth2.json') league_id = "403.l.41177" self.game_type = "nhl" self.game_year = 2020 self.league = None self.setup_menubar() self.my_draft_picks = None self.label_num_picks_before_pick = QLabel( "Number picks until your turn: 4") self.positional_filter_checkboxes = None # runnable to grab draft picks from yahoo self.draft_monitor = None self.draft_complete = False self.draft_results = [] self.position_filtering_layout = QHBoxLayout() self.draft_list_widget = QListWidget() self.projection_table = QTableView() self.projection_table.setSortingEnabled(True) self.pause_draft_button = QPushButton("Pause") self.pause_draft_button.clicked.connect(self.pause_pressed) self.team_combo_box = QComboBox() self.draft_status_label = QLabel("Status: Running") self.roster_table = QTableView() self.roster_table_model = None # self.league_changed("403.l.41177", "nhl", 2020) self.league_changed("406.l.246660", "nfl", 2021) self.initUI() def setup_menubar(self): menuBar = QMenuBar(self) menuBar.setNativeMenuBar(False) self.setMenuBar(menuBar) leagueMenu = menuBar.addMenu("&League") years = [2021, 2020, 2019, 2018] fantasy_games = ['nhl', 'nfl'] for year in years: year_menu = leagueMenu.addMenu(str(year)) for game in fantasy_games: game_menu = year_menu.addMenu(game) gm = yfa.Game(self.oauth, game) ids = gm.league_ids(year) for id in ids: lg = gm.to_league(id) lg_action = QAction(lg.settings()['name'], self) lg_action.league_id = id lg_action.game_type = game lg_action.year = year game_menu.addAction(lg_action) game_menu.triggered[QAction].connect(self.league_selected) def league_selected(self, q): print("league selected") if not (q.league_id == self.league_id and q.year == self.game_year): self.league_changed(q.league_id, q.game_type, q.year) def get_scraped_draft_results(self): scraped_draft_results = None draft_scrape_filename = f".cache/gui_draft/draft-scrape-{self.league_id}-{self.game_type}-{self.game_year}.pkl" if os.path.exists(draft_scrape_filename): with open(draft_scrape_filename, "rb") as f: scraped_draft_results = pickle.load(f) else: scraped_draft_results = retrieve_draft_order(self.league) with open(draft_scrape_filename, "wb") as f: pickle.dump(scraped_draft_results, f) return scraped_draft_results def league_changed(self, league_id, game_type, year): print( f"League changed, id: {league_id} - type: {game_type} - year:{year}" ) if self.draft_monitor is not None and self.league_id != league_id: self.draft_monitor.stop_flag = True print("Draft thread cancelled") self.league_id = league_id self.game_type = game_type self.game_year = year self.league = yfa.league.League(self.oauth, self.league_id) self.my_draft_picks = self._get_draft_picks(self.league.team_key()) self.projections_df = self.retrieve_player_projections() # figure out if we have keepers scraped_draft_results = None if "nfl" == self.game_type: # for keeper leagues, would be defined in a screen scrape. lets do that and simulate them scraped_draft_results = self.get_scraped_draft_results() # assign keepers to teams for team, keepers in scraped_draft_results['keepers'].items(): for keeper in keepers: if 'team' in keeper.keys(): self.projections_df.loc[ self.projections_df.name.str. contains(keeper['name']) & (self.projections_df.team == keeper['team'].upper()), ['draft_fantasy_key', 'is_keeper']] = team, True else: self.projections_df.loc[ self.projections_df.name.str. contains(keeper['name']), ['draft_fantasy_key', 'is_keeper']] = team, True base_columns = App.game_projection_columns[self.game_type].copy() # add league specifc scoring cats for hockey if self.game_type == "nhl": for stat in self.league.stat_categories(): if stat['position_type'] == 'P': base_columns[stat['display_name']] = stat['display_name'] self.projections_model = PandasModel( self.projections_df, column_headers=base_columns, valid_positions=App.game_filter_positions[self.game_type], highlight_personal_ranking_differences=self.game_type == "nhl") self.team_name_by_id = { team['team_key']: team['name'] for team in self.league.teams() } self.team_key_by_name = { team['name']: team['team_key'] for team in self.league.teams() } self.build_filter_panel() self.projections_model.update_filters(self._position_filter_list()) self.draft_complete = False self.draft_results = [] self.projection_table.setModel(self.projections_model) self.pause_draft_button = QPushButton("Pause") self.pause_draft_button.clicked.connect(self.pause_pressed) self.team_combo_box.clear() for team in self.league.teams(): self.team_combo_box.addItem(team['name'], team['team_key']) self.team_combo_box.setCurrentIndex( self.team_combo_box.findData(self.league.team_key())) self.roster_table_model = DraftedRosterModel( self.draft_results, self.league.team_key(), self.projections_df, keepers=scraped_draft_results['keepers'] if scraped_draft_results else None) self.roster_table.setModel(self.roster_table_model) self.draft_list_widget.clear() self.update_picks_until_next_player_pick(0) the_keepers = scraped_draft_results.get( 'keepers', None) if scraped_draft_results else None self.draft_monitor = DraftMonitor(self.league, keepers=the_keepers) self.draft_monitor.register_draft_listener(self) QThreadPool.globalInstance().start(self.draft_monitor) def build_filter_panel(self): ''' builds the position checkbox filters depends on game_type ''' # clear any checkboxes for i in reversed(range(self.position_filtering_layout.count())): self.position_filtering_layout.itemAt(i).widget().close() self.position_filtering_layout.takeAt(i) self.positional_filter_checkboxes = [] for position in self.game_filter_positions[self.game_type]: box = QCheckBox(position) box.setChecked(True) box.clicked.connect(self.filter_checked) self.positional_filter_checkboxes.append(box) self.position_filtering_layout.addWidget(box) all_button = QPushButton('All') all_button.pressed.connect(self.filter_all_selected) self.position_filtering_layout.addWidget(all_button) none_button = QPushButton('None') none_button.pressed.connect(self.filter_none_selected) self.position_filtering_layout.addWidget(none_button) def retrieve_player_projections(self): projections = None if "nhl" == self.game_type: projections = retrieve_yahoo_rest_of_season_projections( self.league.league_id) scoring_stats = [ stat['display_name'] for stat in self.league.stat_categories() if stat['position_type'] == 'P' ] produce_csh_ranking(projections, scoring_stats, projections.index, ranking_column_name='fantasy_score') projections['fantasy_score'] = round(projections['fantasy_score'], 3) projections.reset_index(inplace=True) projections.sort_values("fantasy_score", ascending=False, inplace=True, ignore_index=True) projections['csh_rank'] = projections.index + 1 projections['rank_diff'] = projections['csh_rank'] - projections[ 'current_rank'] else: projections = pd.read_csv( f"{self.game_year}-{self.league_id}-predictions-merged.csv", converters={ "position": lambda x: x.strip("[]").replace('"', "").replace("'", ""). replace(" ", "").split(",") }) projections.sort_values('overall_rank', inplace=True) projections['draft_fantasy_key'] = -1 projections['is_keeper'] = False # use this for rendering eligible positions projections['posn_display'] = projections['position'].apply( lambda x: ", ".join(x)) return projections def draft_status_changed(self, is_paused): self.draft_status_label.setText( "Status: Paused" if is_paused else "Status: Running") def _get_draft_picks(self, team_key): scraped = self.get_scraped_draft_results() if 'predraft' == scraped['status']: return scraped['draft_picks'].get('team_key', []) else: return [ int(key['number']) for key in scraped['draft_picks'][team_key] ] def initUI(self): self.setWindowTitle(self.title) self.setCentralWidget(self.main_widget) self.layout_screen() self.show() def pause_pressed(self): print("Pause pressed") if self.draft_monitor: self.draft_monitor.toggle_paused() if "Pause" == self.pause_draft_button.text(): self.pause_draft_button.setText("Resume") else: self.pause_draft_button.setText("Pause") def handle_team_roster_changed(self, index): print(f"Do something with the selected item: {index.data()}") self.roster_table_model.specify_team_key( self.team_key_by_name[index.data()]) # self.roster_table.setModel(DraftedRosterModel(self.draft_results,self.team_key_by_name[index.data()])) def _set_projection_table_widths(self, table): table.setColumnWidth(0, 140) table.setColumnWidth(1, 60) table.setColumnWidth(2, 60) table.setColumnWidth(3, 50) table.setColumnWidth(4, 50) table.setColumnWidth(5, 50) table.setColumnWidth(6, 50) table.setColumnWidth(7, 60) table.setColumnWidth(8, 50) table.setColumnWidth(9, 50) table.setColumnWidth(10, 50) table.setColumnWidth(11, 50) table.setColumnWidth(12, 50) table.setColumnWidth(13, 50) table.setColumnWidth(14, 50) def hide_drafted_click(self): cbutton = self.sender() self.projections_model.set_hide_drafted(cbutton.isChecked()) def _position_filter_list(self): return { checkbox.text() for checkbox in self.positional_filter_checkboxes if checkbox.isChecked() } def filter_all_selected(self): print("All button pressed") for checkbox in self.positional_filter_checkboxes: checkbox.setChecked(True) self.projections_model.update_filters(self._position_filter_list()) def filter_none_selected(self): print("None pressed") for checkbox in self.positional_filter_checkboxes: checkbox.setChecked(False) self.projections_model.update_filters(self._position_filter_list()) def filter_checked(self, state): print(f"filter was checked: {self.sender().text()}") self.projections_model.update_filters(self._position_filter_list()) def layout_screen(self): layout = QHBoxLayout() left_layout = QVBoxLayout() second_filter_layout = QHBoxLayout() hide_drafted = QCheckBox('Hide drafted') hide_drafted.toggled.connect(self.hide_drafted_click) second_filter_layout.addWidget(hide_drafted) name_filter_layout = QFormLayout() name_filter_text = QLineEdit() name_filter_text.setMaximumWidth(400) name_filter_layout.addRow(QLabel("Name filter: "), name_filter_text) second_filter_layout.addLayout(name_filter_layout) filtering_layout = QVBoxLayout() filtering_layout.addLayout(self.position_filtering_layout) filtering_layout.addLayout(second_filter_layout) second_filter_layout.addStretch(1) # this displays list of eligible players, and their projections self._set_projection_table_widths(self.projection_table) projection_layout = QVBoxLayout() projection_layout.addWidget(self.projection_table) # projection_layout.addWidget(QTableView()) roster_layout = QVBoxLayout() roster_layout.addWidget(self.draft_status_label) roster_team_selection = QHBoxLayout() # set up draft pause button roster_team_selection.addWidget(self.pause_draft_button) roster_team_selection.addWidget(QLabel("Team: ")) self.team_combo_box.view().pressed.connect( self.handle_team_roster_changed) roster_team_selection.addWidget(self.team_combo_box) roster_layout.addLayout(roster_team_selection) game_selection_layout = QHBoxLayout() game_selection_layout.addWidget(self.label_num_picks_before_pick) game_selection_layout.addStretch(1) roster_layout.addWidget(self.roster_table) roster_layout.addWidget(self.draft_list_widget) left_layout.addLayout(game_selection_layout) left_layout.addLayout(filtering_layout) left_layout.addLayout(projection_layout) layout.addLayout(left_layout, stretch=2) layout.addLayout(roster_layout) self.main_widget.setLayout(layout) def player_drafted(self, draft_entry): self.draft_results.append(draft_entry) print(f"de: {draft_entry}") player_id = int(draft_entry['player_key'].split('.')[-1]) draft_position = int(draft_entry['pick']) player_name = None draft_value = 'n/a' try: player_name = self.projections_df[self.projections_df.player_id == player_id]['name'].values[0] # figure out draft position relative to csh_rank ranking try: csh_rank = self.projections_df[self.projections_df.player_id == player_id]['csh_rank'].values[0] draft_value = draft_position - csh_rank except KeyError: pass except IndexError: player_name = f"Unknown({player_id})" team_id = int(draft_entry['team_key'].split('.')[-1]) self.projections_model.player_drafted(player_id, team_id) self.roster_table_model.player_drafted(draft_entry) try: next_draft = next(x for x in self.my_draft_picks if x > draft_position) self.label_num_picks_before_pick.setText( f"Number picks until your turn: {next_draft-draft_position}") except StopIteration: pass self.draft_list_widget.insertItem( 0, f"{draft_position}. {player_name} - {self.team_name_by_id[draft_entry['team_key']]} - ({draft_value})" ) def update_picks_until_next_player_pick(self, current_draft_position): try: next_draft = next(x for x in self.my_draft_picks if x > current_draft_position) self.label_num_picks_before_pick.setText( f"Number picks until your turn: {next_draft-current_draft_position}" ) except StopIteration: pass
class Whittler(GeneralWindow): def __init__(self, screen_width, screen_height): self.voting_dict = {} self.folder_path = None self.photo_picker_dict = {} self.whittled = False self.main_photo_file_path = None self.screen_width = screen_width self.screen_height = screen_height self.checkmark_icon = QIcon(CHECKMARK_ICON_PATH) self.x_mark_icon = QIcon(X_MARK_ICON_PATH) super().__init__(screen_width, screen_height) self.create_actions() self.create_menus() self.setWindowTitle("Photo Whittler") self.initUI() state = self.load_state() if state is not None: if state.whittled: return if state is not None and os.path.exists(str(state.folder_path)): self.load_save_dialog_box(state) def _get_width(self, object_width): return get_width(self.screen_width, object_width) def _get_height(self, object_height): return get_height(self.screen_height, object_height) def eventFilter(self, source, event): # Over-ride the scrolling of the key presses to move between main photos along the photo # picker if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Right: self._set_next_photo_as_main() elif event.key() == Qt.Key_Left: self._set_previous_photo_as_main() elif event.key() == Qt.Key_Up: self._upvote_main_photo() elif event.key() == Qt.Key_Down: self._downvote_main_photo() return True return super(Whittler, self).eventFilter(source, event) def initUI(self): self.windowLayout = QVBoxLayout() self.workingLayout = QHBoxLayout() self.photoLayout = QVBoxLayout() self.actionsLayout = QVBoxLayout() self.missionControlLayout = QHBoxLayout() self.buttonLayout = QHBoxLayout() self.selectedImage = QLabel() self.photoLayout.addWidget(self.selectedImage) self.upvote_button = QPushButton("Upvote", self) self.upvote_button.setStyleSheet(UPVOTE_BUTTON_STYLE) self.downvote_button = QPushButton("Downvote", self) self.downvote_button.setStyleSheet(DOWNVOTE_BUTTON_STYLE) self.statsLabel = QLabel(self) self.statsLabel.setStyleSheet(LIGHT_TEXT_COLOR) self.whittle_button = QPushButton("Whittle", self) self.whittle_button.setStyleSheet(WHITTLE_BUTTON_STYLE) self.whittle_button.clicked.connect(self.whittle) self.actionsLayout.addLayout(self.missionControlLayout) self.actionsLayout.addLayout(self.buttonLayout) self.actionsLayout.setContentsMargins(10, 0, 10, 0) self.missionControlLayout.addWidget(self.statsLabel, alignment=Qt.AlignLeft) self.missionControlLayout.addWidget(self.whittle_button, alignment=Qt.AlignRight) self.buttonLayout.addWidget(self.upvote_button, alignment=Qt.AlignLeft) self.buttonLayout.addWidget(self.downvote_button, alignment=Qt.AlignRight) # self.workingLayout. self.workingLayout.addLayout(self.photoLayout, 2) self.workingLayout.addLayout(self.actionsLayout, 1) self.scroll_area = QScrollArea(self) self.scroll_area.installEventFilter(self) self.scroll_area.setWidgetResizable(True) self.scroll_area.setFixedHeight(self._get_height(PHOTO_PICKER_HEIGHT)) self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) widget_content = QWidget() self.scroll_area.setWidget(widget_content) self.photoPickerLayout = QHBoxLayout(widget_content) self.photoPickerLayout.setSpacing(5) self.windowLayout.addLayout(self.workingLayout, 2) self.windowLayout.addWidget(self.scroll_area) widget = QWidget() widget.setLayout(self.windowLayout) self.setCentralWidget(widget) def create_actions(self): self.openFolderAct = QAction("&Open Folder", self, shortcut="Ctrl+O", triggered=self.open_folder) self.organizeFolderAct = QAction("&Organize Folder", self, shortcut="Ctrl+N", triggered=self.organize_folder) self.clearSateStateAct = QAction("&Clear Save Sate", self, shortcut="Ctrl+E", triggered=self.clear_save_state) self.archivePhotosAct = QAction("&Organize Folder", self, shortcut="Ctrl+M", triggered=self.archive_photos) def create_menus(self): self.fileMenu = QMenu("&File", self) self.fileMenu.addAction(self.openFolderAct) self.fileMenu.addAction(self.archivePhotosAct) self.fileMenu.addAction(self.organizeFolderAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.clearSateStateAct) self.menuBar().addMenu(self.fileMenu) def load_save_dialog_box(self, state): dialog = LoadSaveDialog() if dialog.exec_(): self.folder_path = state.folder_path self.whittled = state.whittled self.open_folder(state.folder_path) self.voting_dict = state.voting_dict self._set_stats() for file_name, wf_set in self.voting_dict.items(): wf = next(iter(wf_set)) voted_photo_button = self.photo_picker_dict[wf.file_name][0] thumbnail_file_path = self.photo_picker_dict[wf.file_name][1] if wf.edit_file is not None: if wf.edit_file: icon_path = CHECKMARK_ICON_PATH else: icon_path = X_MARK_ICON_PATH self._update_button_with_vote( voted_photo_button, thumbnail_file_path, icon_path, ) return def whittle(self): if not self.voting_dict: ErrorDialog( error="Unable to Whittle files", error_reason= "No photos imported to Whittler to take action on.", ).exec_() return upvote_file_paths = [] os.makedirs(os.path.join(self.folder_path, UPVOTED_FOLDER_NAME), exist_ok=True) for file_name, wf_set in self.voting_dict.items(): raw_w_file = next( (wf for wf in wf_set if {wf.file_extension}.issubset(RAW_EXTENSIONS)), None) if raw_w_file is None: continue if not raw_w_file.edit_file: continue upvoted_file_path = os.path.join( self.folder_path, UPVOTED_FOLDER_NAME, f"{raw_w_file.file_name}{raw_w_file.file_extension}") upvote_file_paths.append((raw_w_file.file_path, upvoted_file_path)) if not upvote_file_paths: ErrorDialog( error="Unable to Whittle files", error_reason= "No photos upvoted. Please upvote photos to allow Whittling.", ).exec_() return with ThreadPoolExecutor() as tpe: tpe.map(thread_safe_file_copy, upvote_file_paths) self.whittled = True def clear_save_state(self): if os.path.exists(SAVE_STATE_PATH): os.remove(SAVE_STATE_PATH) def open_folder(self, folder_path=None): self.voting_dict = {} self.photo_picker_dict = {} if not os.path.exists(str(folder_path)): folder_path = None if folder_path is None: self.folder_path = QFileDialog.getExistingDirectory( self, "Open Folder", QDir.currentPath()) else: self.folder_path = folder_path if self.folder_path == "": return for root, dir, files in os.walk(self.folder_path): for file in files: wf = WhittleFile(os.path.join(root, file)) if {wf.file_extension}.issubset(VALID_EXTENSIONS): if self.voting_dict.get(wf.file_name) is None: self.voting_dict[wf.file_name] = {wf} else: self.voting_dict[wf.file_name].add(wf) if not self.voting_dict: ErrorDialog( error="Unable to open folder", error_reason= "Unable to locate any valid photo files at folder path. " "Please select a new folder to begin Whittling.", extra_info=f'Folder path: "{self.folder_path}".').exec_() return # filter out file_names that don't have a JPG whittle file as we want to load those into # memory self.voting_dict = { file_name: wf_set for file_name, wf_set in self.voting_dict.items() if any([wf.file_extension == JPG_EXTENSION for wf in wf_set]) } if not self.voting_dict: ErrorDialog( error="Unable to open folder", error_reason= "Unable to locate any valid JPG files at folder path. " "Please select a new folder to begin Whittling.", extra_info=f'Folder path: "{self.folder_path}".').exec_() return # before we create new thumbnails, delete any that exit in cwd self._clean_up_temp() file_names = list(self.voting_dict.keys()) thumbnail_save_info = [] for file_name in file_names: wf = next(wf for wf in self.voting_dict[file_name] if wf.file_extension == JPG_EXTENSION) thumbnail_save_info.append([ wf.file_path, self._get_width(THUMBNAIL_WIDTH), self._get_height(THUMBNAIL_HEIGHT) ]) with ThreadPoolExecutor() as tpe: results = tpe.map(save_image_as_thumbnail, thumbnail_save_info) photo_file_paths = list(results) photo_file_paths.sort() self._clear_programmatically_populated_layouts() self._populate_photo_picker(photo_file_paths) full_res = self._get_full_res_photo_path(photo_file_paths[0]) self._set_main_photo(full_res) self._set_stats() def organize_folder(self, folder_path=None): if folder_path is None: organize_folder = QFileDialog.getExistingDirectory( self, "Open Folder", QDir.currentPath()) if organize_folder == "": return else: organize_folder = folder_path organize_dict = {} for file in os.listdir(organize_folder): wf = WhittleFile(os.path.join(organize_folder, file)) if {wf.file_extension}.issubset(VALID_EXTENSIONS): if organize_dict.get(wf.file_name) is None: organize_dict[wf.file_name] = {wf} else: organize_dict[wf.file_name].add(wf) if not organize_dict: ErrorDialog( error="Unable to organize folder", error_reason= "Unable to locate any photo files with valid file extension types.", extra_info=f'Supported extension types: "{*VALID_EXTENSIONS,}".' ).exec_() return organize_dict = { file_name: wf_set for file_name, wf_set in organize_dict.items() if len(wf_set) == 2 } if not organize_dict: ErrorDialog( error="Unable to organize folder", error_reason= "Folder to organize did not contain any files where there were 2 file " "types per unique photo.", ).exec_() return file_extensions = [] for file_name, wf_set in organize_dict.items(): for wf in wf_set: file_extensions.append(wf.file_extension) file_extensions = set(file_extensions) if len(file_extensions) != 2: ErrorDialog( error="Unable to organize folder", error_reason= "Folder to organize contained more than two file extensions.", extra_info=f'File Extensions: "{*file_extensions,}"').exec_() return raw_unedited_folder_path = (os.path.join(organize_folder, RAW_UNEDITED_FOLDER_NAME)) jpeg_unedited_folder_path = (os.path.join(organize_folder, JPG_UNEDITED_FOLDER_NAME)) os.makedirs(raw_unedited_folder_path, exist_ok=True) os.makedirs(jpeg_unedited_folder_path, exist_ok=True) file_movement_list = [] for file_name, wf_set in organize_dict.items(): for wf in wf_set: if {wf.file_extension}.issubset(RAW_EXTENSIONS): file_movement_list.append( (wf.file_path, os.path.join(raw_unedited_folder_path, wf.file_name + wf.file_extension))) else: file_movement_list.append( (wf.file_path, os.path.join(jpeg_unedited_folder_path, wf.file_name + wf.file_extension))) with ThreadPoolExecutor() as tpe: tpe.map(thread_safe_file_move, file_movement_list) ActionCompleteDialog( action="Folder Organization", action_message= f'Finished organizing "{os.path.basename(organize_folder)}" folder.' ).exec_() def archive_photos(self): unarchived_location = QFileDialog.getExistingDirectory( self, "Select Unarchived Photo Location", QDir.currentPath()) if unarchived_location == "": return # maybe make this part of a config that can be set? archive_location = QFileDialog.getExistingDirectory( self, "Select Archive Location", QDir.currentPath()) ok, archive_name = QInputDialog.getText(self, "Archive Name", "Enter Name") if ok and archive_name: pass elif not ok: return elif not archive_name: ErrorDialog( error="Unable to create archive folder", error_reason="No name was passed in for archive.", ).exec_() return archive_path = os.path.join(archive_location, archive_name) os.mkdir(archive_path, exist_ok=True) file_paths_to_archive = [] for root, dir, files in os.walk(unarchived_location): for file in files: wf = WhittleFile(os.path.join(root, file)) if {wf.file_extension}.issubset(VALID_EXTENSIONS): files_to_archive.append( (wf.file_path, os.path.join(archive_path, wf.file_name + wf.file_extension))) with ThreadPoolExecutor() as tpe: tpe.map(thread_safe_file_move, file_paths_to_archive) dialog = OrganizeArchiveDialog() if dialog.exec_(): self.organize_folder(archive_path) else: ActionCompleteDialog( action="Folder Archiving", action_message= f'Finished archiving photos from "{archive_name}" to ' f'"{os.path.basename(unarchived_location)}".').exec_() def _get_full_res_photo_path(self, photo_file_path): file_name = WhittleFile(photo_file_path).file_name return next(wf.file_path for wf in self.voting_dict[file_name] if wf.file_extension == JPG_EXTENSION) def _set_main_photo(self, file_path): if not os.path.exists(str(file_path)): return self.main_photo_file_path = file_path pixmap = QPixmap(file_path) pixmap = pixmap.scaled(self._get_width(MAIN_PHOTO_HEIGHT_AND_WIDTH), self._get_height(MAIN_PHOTO_HEIGHT_AND_WIDTH), Qt.KeepAspectRatio) self.selectedImage.setPixmap(pixmap) try: self.upvote_button.clicked.disconnect() except Exception: pass try: self.downvote_button.clicked.disconnect() except Exception: pass self.upvote_button.clicked.connect(self._upvote_main_photo) self.downvote_button.clicked.connect(self._downvote_main_photo) # Reset the color of all other buttons before setting new button color for file_name, photo_tuple in self.photo_picker_dict.items(): photo_tuple[0].setStyleSheet(PICKER_BUTTON_STYLE) # Setting the color of the selected button to be a little lighter, for a nice touch photo_button = self.photo_picker_dict[WhittleFile( file_path).file_name][0] photo_button.setStyleSheet(SELECTED_PICKER_BUTTON_STYLE) def _set_next_photo_as_main(self): # Move along photo_picker_dict to set main photos if not os.path.exists(str(self.main_photo_file_path)): return None current_file_name = WhittleFile(self.main_photo_file_path).file_name next_file_name = get_next_key(self.photo_picker_dict, current_file_name) if next_file_name is None: return None full_res = next(wf.file_path for wf in self.voting_dict[next_file_name] if wf.file_extension == JPG_EXTENSION) self._set_main_photo(full_res) def _set_previous_photo_as_main(self): # Move along photo_picker_dict to set main photos if not os.path.exists(str(self.main_photo_file_path)): return None current_file_name = WhittleFile(self.main_photo_file_path).file_name previous_file_name = get_previous_key(self.photo_picker_dict, current_file_name) if previous_file_name is None: return None full_res = next(wf.file_path for wf in self.voting_dict[previous_file_name] if wf.file_extension == JPG_EXTENSION) self._set_main_photo(full_res) def _populate_photo_picker(self, photo_file_paths: List): def _get_icon(photo_file_path): return QIcon(photo_file_path) if not photo_file_paths: return with ThreadPoolExecutor() as tpe: results = tpe.map(_get_icon, photo_file_paths) icons = list(results) for photo_file_path, icon in zip(photo_file_paths, icons): photo_button = QPushButton() photo_button.setStyleSheet(PICKER_BUTTON_STYLE) photo_button.setIcon(icon) photo_button.setIconSize( QSize( self._get_width(THUMBNAIL_WIDTH), self._get_height(THUMBNAIL_HEIGHT), )) photo_button.setFixedSize(self._get_width(PICKER_BUTTON_WIDTH), self._get_height(PICKER_BUTTON_HEIGHT)) full_res_file_path = self._get_full_res_photo_path(photo_file_path) photo_button.clicked.connect( lambda checked, _path=full_res_file_path: self._set_main_photo( _path)) self.photoPickerLayout.addWidget(photo_button) self.photo_picker_dict.update({ WhittleFile(photo_file_path).file_name: (photo_button, photo_file_path) }) def _clear_programmatically_populated_layouts(self): while self.photoPickerLayout.count(): child = self.photoPickerLayout.takeAt(0) if child.widget(): child.widget().deleteLater() self.selectedImage.setPixmap(QPixmap()) def _upvote_main_photo(self): if not os.path.exists(str(self.main_photo_file_path)): return None current_file_name = WhittleFile(self.main_photo_file_path).file_name wf_set = self.voting_dict.get(current_file_name) if wf_set is None: return for wf in wf_set: wf.edit_file = True upvoted_photo_button = self.photo_picker_dict[current_file_name][0] thumbnail_file_path = self.photo_picker_dict[current_file_name][1] self._update_button_with_vote( upvoted_photo_button, thumbnail_file_path, CHECKMARK_ICON_PATH, ) self.voting_dict[current_file_name] = wf_set self._set_stats() def _downvote_main_photo(self): if not os.path.exists(str(self.main_photo_file_path)): return None current_file_name = WhittleFile(self.main_photo_file_path).file_name wf_set = self.voting_dict.get(current_file_name) if wf_set is None: return for wf in wf_set: wf.edit_file = False downvoted_photo_button = self.photo_picker_dict[current_file_name][0] thumbnail_file_path = self.photo_picker_dict[current_file_name][1] self._update_button_with_vote( downvoted_photo_button, thumbnail_file_path, X_MARK_ICON_PATH, ) self.voting_dict[current_file_name] = wf_set self._set_stats() @staticmethod def _threaded_file_move(file_move_tuple): current_location = "" destination_location = "" shutil.move() @staticmethod def _clean_up_temp(): if not os.path.exists(TEMP_PATH): os.makedirs(TEMP_PATH, exist_ok=True) thumbnails = [ file for file in os.listdir(os.path.join(TEMP_PATH)) if file.lower().endswith(JPG_EXTENSION) ] if thumbnails: for file in thumbnails: path = os.path.join(TEMP_PATH, file) os.remove(path) def _update_button_with_vote(self, button, thumbnail_path, icon_path): temp_icon = QIcon() main_image_pixmap = QPixmap(thumbnail_path) icon_image_pixmap = QPixmap(icon_path) result = join_pixmap( main_image_pixmap, icon_image_pixmap, self.screen_width, self.screen_height, ) temp_icon.addPixmap(result) button.setIcon(temp_icon) button.setIconSize( QSize( self._get_width(THUMBNAIL_WIDTH), self._get_height(THUMBNAIL_HEIGHT), )) def _set_stats(self): number_of_photos = f"Number of Photos: {len(self.voting_dict.keys())}" upvotes = 0 downvotes = 0 for file_path, wf_set in self.voting_dict.items(): if next(wf for wf in wf_set).edit_file == True: upvotes += 1 elif next(wf for wf in wf_set).edit_file == False: downvotes += 1 number_of_upvotes = f"Number of Upvotes: {upvotes}" number_of_downvotes = f"Number of Downvotes: {downvotes}" stats = (f"Whittle Statistics\n" f"{number_of_photos}\n" f"{number_of_upvotes}\n" f"{number_of_downvotes}") self.statsLabel.setText(stats) def save_state(self): if os.path.exists(SAVE_STATE_PATH): os.remove(SAVE_STATE_PATH) with open(SAVE_STATE_PATH, "wb") as file: pickle.dump( WhittleState(self.folder_path, self.voting_dict, self.whittled), file, pickle.HIGHEST_PROTOCOL) @staticmethod def load_state(): if os.path.exists(SAVE_STATE_PATH): with open(SAVE_STATE_PATH, "rb") as file: return pickle.load(file) return None def closeEvent(self, event): self._clean_up_temp() self.save_state() event.accept()
class GuiReplayer(QWidget): """A Replayer to replay hands.""" def __init__(self, config, querylist, mainwin, handlist): QWidget.__init__(self, None) self.setFixedSize(800, 680) self.conf = config self.main_window = mainwin self.sql = querylist self.db = Database.Database(self.conf, sql=self.sql) self.states = [] # List with all table states. self.handlist = handlist self.handidx = 0 self.setWindowTitle("FPDB Hand Replayer") self.replayBox = QVBoxLayout() self.setLayout(self.replayBox) self.replayBox.addStretch() self.buttonBox = QHBoxLayout() self.prevButton = QPushButton("Prev") self.prevButton.clicked.connect(self.prev_clicked) self.prevButton.setFocusPolicy(Qt.NoFocus) self.startButton = QPushButton("Start") self.startButton.clicked.connect(self.start_clicked) self.startButton.setFocusPolicy(Qt.NoFocus) self.endButton = QPushButton("End") self.endButton.clicked.connect(self.end_clicked) self.endButton.setFocusPolicy(Qt.NoFocus) self.playPauseButton = QPushButton("Play") self.playPauseButton.clicked.connect(self.play_clicked) self.playPauseButton.setFocusPolicy(Qt.NoFocus) self.nextButton = QPushButton("Next") self.nextButton.clicked.connect(self.next_clicked) self.nextButton.setFocusPolicy(Qt.NoFocus) self.replayBox.addLayout(self.buttonBox) self.stateSlider = QSlider(Qt.Horizontal) self.stateSlider.valueChanged.connect(self.slider_changed) self.stateSlider.setFocusPolicy(Qt.NoFocus) self.replayBox.addWidget(self.stateSlider, False) self.playing = False self.tableImage = None self.playerBackdrop = None self.cardImages = None self.deck_inst = Deck.Deck(self.conf, height=CARD_HEIGHT, width=CARD_WIDTH) self.show() def renderCards(self, painter, cards, x, y): for card in cards: cardIndex = Card.encodeCard(card) painter.drawPixmap(QPoint(x, y), self.cardImages[cardIndex]) x += self.cardwidth def paintEvent(self, event): if self.tableImage is None or self.playerBackdrop is None: try: self.playerBackdrop = QImage( os.path.join(self.conf.graphics_path, u"playerbackdrop.png")) self.tableImage = QImage( os.path.join(self.conf.graphics_path, u"Table.png")) except: return if self.cardImages is None: self.cardwidth = CARD_WIDTH self.cardheight = CARD_HEIGHT self.cardImages = [None] * 53 suits = ('s', 'h', 'd', 'c') ranks = (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2) for j in range(0, 13): for i in range(0, 4): index = Card.cardFromValueSuit(ranks[j], suits[i]) self.cardImages[index] = self.deck_inst.card( suits[i], ranks[j]) self.cardImages[0] = self.deck_inst.back() if not event.rect().intersects( QRect(0, 0, self.tableImage.width(), self.tableImage.height())): return painter = QPainter(self) painter.drawImage(QPoint(0, 0), self.tableImage) if len(self.states) == 0: return state = self.states[self.stateSlider.value()] communityLeft = int(self.tableImage.width() / 2 - 2.5 * self.cardwidth) communityTop = int(self.tableImage.height() / 2 - 1.75 * self.cardheight) convertx = lambda x: int(x * self.tableImage.width() * 0.8 ) + self.tableImage.width() / 2 converty = lambda y: int(y * self.tableImage.height() * 0.6 ) + self.tableImage.height() / 2 for player in state.players.values(): playerx = convertx(player.x) playery = converty(player.y) painter.drawImage( QPoint(playerx - self.playerBackdrop.width() / 2, playery - 3), self.playerBackdrop) if player.action == "folds": painter.setPen(QColor("grey")) else: painter.setPen(QColor("white")) x = playerx - self.cardwidth * len(player.holecards) / 2 self.renderCards(painter, player.holecards, x, playery - self.cardheight) painter.drawText( QRect(playerx - 100, playery, 200, 20), Qt.AlignCenter, '%s %s%.2f' % (player.name, self.currency, player.stack)) if player.justacted: painter.setPen(QColor("yellow")) painter.drawText(QRect(playerx - 50, playery + 15, 100, 20), Qt.AlignCenter, player.action) else: painter.setPen(QColor("white")) if player.chips != 0: painter.drawText( QRect( convertx(player.x * .65) - 100, converty(player.y * 0.65), 200, 20), Qt.AlignCenter, '%s%.2f' % (self.currency, player.chips)) painter.setPen(QColor("white")) if state.pot > 0: painter.drawText( QRect(self.tableImage.width() / 2 - 100, self.tableImage.height() / 2 - 20, 200, 40), Qt.AlignCenter, '%s%.2f' % (self.currency, state.pot)) for street in state.renderBoard: x = communityLeft if street.startswith('TURN'): x += 3 * self.cardwidth elif street.startswith('RIVER'): x += 4 * self.cardwidth y = communityTop if street.endswith('1'): # Run it twice streets y -= 0.5 * self.cardheight elif street.endswith('2'): y += 0.5 * self.cardheight self.renderCards(painter, state.board[street], x, y) def keyPressEvent(self, event): if event.key() == Qt.Key_Left: self.stateSlider.setValue(max(0, self.stateSlider.value() - 1)) elif event.key() == Qt.Key_Right: self.stateSlider.setValue( min(self.stateSlider.maximum(), self.stateSlider.value() + 1)) elif event.key() == Qt.Key_Up: if self.handidx < len(self.handlist) - 1: self.play_hand(self.handidx + 1) elif event.key() == Qt.Key_Down: if self.handidx > 0: self.play_hand(self.handidx - 1) else: QWidget.keyPressEvent(self, event) def play_hand(self, handidx): self.handidx = handidx hand = Hand.hand_factory(self.handlist[handidx], self.conf, self.db) # hand.writeHand() # Print handhistory to stdout -> should be an option in the GUI self.currency = hand.sym self.states = [] state = TableState(hand) seenStreets = [] for street in hand.allStreets: if state.called > 0: for player in state.players.values(): if player.stack == 0: state.allin = True break if not hand.actions[street] and not state.allin: break seenStreets.append(street) state = copy.deepcopy(state) state.startPhase(street) self.states.append(state) for action in hand.actions[street]: state = copy.deepcopy(state) state.updateForAction(action) self.states.append(state) state = copy.deepcopy(state) state.endHand(hand.collectees, hand.pot.returned) self.states.append(state) # Clear and repopulate the row of buttons for idx in reversed(range(self.buttonBox.count())): self.buttonBox.takeAt(idx).widget().setParent(None) self.buttonBox.addWidget(self.prevButton) self.prevButton.setEnabled(self.handidx > 0) self.buttonBox.addWidget(self.startButton) for street in hand.allStreets[1:]: btn = QPushButton(street.capitalize()) self.buttonBox.addWidget(btn) btn.clicked.connect(partial(self.street_clicked, street=street)) btn.setEnabled(street in seenStreets) btn.setFocusPolicy(Qt.NoFocus) self.buttonBox.addWidget(self.endButton) self.buttonBox.addWidget(self.playPauseButton) self.buttonBox.addWidget(self.nextButton) self.nextButton.setEnabled(self.handidx < len(self.handlist) - 1) self.stateSlider.setMaximum(len(self.states) - 1) self.stateSlider.setValue(0) self.update() def increment_state(self): if self.stateSlider.value() == self.stateSlider.maximum(): self.playing = False self.playPauseButton.setText("Play") if self.playing: self.stateSlider.setValue(self.stateSlider.value() + 1) def slider_changed(self, value): self.update() def importhand(self, handid=1): h = Hand.hand_factory(handid, self.conf, self.db) return h def play_clicked(self, checkState): self.playing = not self.playing if self.playing: self.playPauseButton.setText("Pause") self.playTimer = QTimer() self.playTimer.timeout.connect(self.increment_state) self.playTimer.start(1000) else: self.playPauseButton.setText("Play") self.playTimer = None def start_clicked(self, checkState): self.stateSlider.setValue(0) def end_clicked(self, checkState): self.stateSlider.setValue(self.stateSlider.maximum()) def prev_clicked(self, checkState): self.play_hand(self.handidx - 1) def next_clicked(self, checkState): self.play_hand(self.handidx + 1) def street_clicked(self, checkState, street): for i, state in enumerate(self.states): if state.street == street: self.stateSlider.setValue(i) break
class ConfigUI(QWidget): configFilePath = "" parseConfigSignal = pyqtSignal(etree._Element) selectResGroupSignal = pyqtSignal(list) def __init__(self, parent=None): super(ConfigUI, self).__init__(parent) grid = QGridLayout() self.setLayout(grid) ## grid.addWidget(QLabel("配置文件:"), 0, 0) grid.addWidget(QLabel("资源分组:"), 1, 0) grid.addWidget(QLabel("数据编码:"), 2, 0) ## self.configFileLE = QLineEdit() # self.configFileLE.setEnabled(False) self.configFileLE.setFocusPolicy(Qt.NoFocus) grid.addWidget(self.configFileLE, 0, 1) browsePB = QPushButton("浏览") browsePB.clicked.connect(self.browse_config_path) grid.addWidget(browsePB, 0, 2) ## self.resGroupWidget = QWidget() self.resGroupLayout = QHBoxLayout() self.resGroupWidget.setLayout(self.resGroupLayout) grid.addWidget(self.resGroupWidget, 1, 1) selectPB = QPushButton("选择") selectPB.clicked.connect(self.select_res_group) grid.addWidget(selectPB, 1, 2) # def create_config def browse_config_path(self): open = QFileDialog() # self.configFilePath = open.getOpenFileUrl(None, "选择转换列表文件") self.configFilePath = open.getOpenFileName(None, "选择转换列表文件", "./") self.configFileLE.setText(self.configFilePath[0]) if self.configFilePath[0] != "": list = etree.parse(self.configFilePath[0]) root = list.getroot() for item in root: if item.tag == "ConvTree": # 转换树 self.parseConfigSignal.emit(item) elif item.tag == "ResStyleList": # 资源分组 self.parse_res_group(item) pass def select_res_group(self): groups = self.resGroupWidget.children() if len(groups) > 0: resGroupArr = [] for item in groups: if isinstance(item, QCheckBox): if item.isChecked(): resGroupArr.append(int(item.text().split(" ")[1])) self.selectResGroupSignal.emit(resGroupArr) def parse_res_group(self, item): while self.resGroupLayout.count(): self.resGroupLayout.takeAt(0) for node in item: if node.tag != "ResStyle": continue print(node.attrib["Name"]) checkBox = QCheckBox(node.attrib["Name"] + " " + str(node.attrib["ID"])) self.resGroupLayout.addWidget(checkBox) self.resGroupLayout.addStretch()
class MyWindow(QWidget): '''main window''' def __init__(self): super().__init__() self.accs = {} self.active_accs = [] self.init_directory() self.init_accounts() self.init_window() self.init_widgets() self.show() sys.exit(app.exec_()) def init_directory(self): if not os.path.isdir('tmp'): os.mkdir('tmp') def init_accounts(self): '''load account AT and AS from local and create api object and stream''' if not os.path.exists("images"): os.mkdir("images") if os.path.isfile("auth.json"): with open('auth.json', 'r') as f: authdic = json.load(f) for name, keys in authdic["Twitter"].items(): api = twitter.connect(keys["ACCESS_TOKEN"], keys["ACCESS_SECRET"]) self.accs[name] = {'api': api} if not glob.glob("images/" + name + ".*"): self.accs[name]['icon_path'] = twitter.getmyicon(api, name) else: self.accs[name]['icon_path'] = glob.glob("images/" + name + ".*")[0] else: default = {"Twitter": {}, "Mastodon": {}} with open('auth.json', 'w') as f: json.dump(default, f, indent=2) self.authdic = {} def init_window(self): #self.setGeometry(300, 100, 200, 125) self.setWindowTitle("myojo") self.setWindowIcon(QIcon('icon_myojo.ico')) def init_widgets(self): self.whole_vbox = QVBoxLayout(self) self.upper_hbox = QHBoxLayout() middle_hbox = QHBoxLayout() self.lower_hbox = QHBoxLayout() add_account_pushbutton = QPushButton('+') add_account_pushbutton.clicked.connect(self.add_acc) self.upper_hbox.addWidget(add_account_pushbutton) for key, value in self.accs.items(): acc_pushbutton = QPushButton(QIcon(value['icon_path']), None, None) acc_pushbutton.setWhatsThis(key) acc_pushbutton.setCheckable(True) acc_pushbutton.toggled.connect(self.choose_acc) self.upper_hbox.addWidget(acc_pushbutton) self.compose_textedit = MyComposer(self, self.attach_show) middle_hbox.addWidget(self.compose_textedit) self.submit_pushbutton = QPushButton('submit') self.submit_pushbutton.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored) self.submit_pushbutton.clicked.connect(self.submit) middle_hbox.addWidget(self.submit_pushbutton) self.lower_hbox.addStretch() self.whole_vbox.addLayout(self.upper_hbox) self.whole_vbox.addLayout(middle_hbox) self.whole_vbox.addLayout(self.lower_hbox) def submit(self): if not self.active_accs: return submit_text = self.compose_textedit.toPlainText() submit_images = self.compose_textedit.attached_images if not submit_images: if not submit_text: return False for key in self.active_accs: self.accs[key]['api'].update_status(submit_text) else: for key in self.active_accs: media_ids = [ self.accs[key]['api'].media_upload(i).media_id_string for i in submit_images ] self.accs[key]['api'].update_status(status=submit_text, media_ids=media_ids) self.compose_textedit.setPlainText("") self.compose_textedit.attached_images = [] while self.lower_hbox.count() > 1: item = self.lower_hbox.takeAt(0) if not item: continue w = item.widget() if w: w.deleteLater() def add_acc(self): api, name = twitter.authentication() self.accs[name] = {'api': api} if not glob.glob("images/" + name + ".*"): self.accs[name]['icon_path'] = twitter.getmyicon(api, name) else: self.accs[name]['icon_path'] = glob.glob("images/" + name + ".*")[0] acc_pushbutton = QPushButton(QIcon(self.accs[name]['icon_path']), None, None) acc_pushbutton.setWhatsThis(name) acc_pushbutton.setCheckable(True) acc_pushbutton.toggled.connect(self.choose_acc) self.upper_hbox.addWidget(acc_pushbutton) def choose_acc(self): acc = self.sender() if acc.isChecked(): self.active_accs.append(acc.whatsThis()) else: self.active_accs.remove(acc.whatsThis()) def attach_show(self, Qimg=None, filename=None): image_cnt = self.lower_hbox.count() if image_cnt == 5: return False attached_label = IconLabel(self, filename) if Qimg is not None: attached_pixmap = QPixmap.fromImage(Qimg) elif filename is not None: attached_pixmap = QPixmap(filename) attached_pixmap = attached_pixmap.scaled(QSize(60, 60), 1, 1) attached_label.setPixmap(attached_pixmap) self.lower_hbox.insertWidget(image_cnt - 1, attached_label) return True def closeEvent(self, event): for tmp in os.listdir('tmp'): filename = 'tmp/' + tmp os.remove(filename)
class SaveDataWindow(QWidget): def __init__(self, dest, schema, callback, parent=None): super(SaveDataWindow, self).__init__() self.dests = ["console", "text", "file", "database"] self.dest = dest self.schema = schema #Determine screen settings geo = self.frameGeometry() self.width = QDesktopWidget().availableGeometry().width() self.height = QDesktopWidget().availableGeometry().height() #Define window par meters self.resize(self.width * .5, self.height * .5) self.setWindowTitle("Aqueti Schema Editor") # self.mainLayout = QVBoxLayout() self.titleLayout = QHBoxLayout() self.destLayout = QHBoxLayout() #Create title title = QLabel() title.setText("Schema Saving Dialog") self.titleLayout.addWidget(title) self.mainLayout.addLayout(self.titleLayout) #Destination Layout self.destLayout = QHBoxLayout() self.mainLayout.addLayout(self.destLayout) #Add Button Layout self.buttonLayout = QHBoxLayout() self.submitButton = QPushButton("Save") self.submitButton.clicked.connect(lambda: self.saveButtonCallback()) self.buttonLayout.addWidget(self.submitButton) cancelButton = QPushButton("Cancel") cancelButton.clicked.connect(lambda: self.cancelButtonCallback()) self.buttonLayout.addWidget(cancelButton) self.mainLayout.addLayout(self.buttonLayout) self.setLayout(self.mainLayout) self.show() self.updateDestLayout() self.draw() ## # \brief updates the destinatino layout # def updateDestLayout(self): #Remove current layout information #Remove all widgets from the current layout while self.destLayout.count(): item = self.destLayout.takeAt(0) self.destLayout.removeItem(item) widget = item.widget() if widget is not None: widget.deleteLater() try: item.deleteLater() except: pass ############################################# # Layout to select a destination ############################################# destTitle = QLabel() destTitle.setText("OutputType:") self.destCombo = QComboBox() self.destCombo.addItems(self.dests) #Find what our current dest is and set the appropriate index index = 0 for i in range(0, self.destCombo.count()): if self.destCombo.itemText(i) == self.dest["type"]: index = i self.destCombo.setCurrentIndex(index) self.destLayout.addWidget(destTitle) self.destLayout.addWidget(self.destCombo) #### # Fill in details base on source tpye #### if self.dest["type"] == "console": pass elif self.dest["type"] == "file": fileLabel = QLabel() fileLabel.setText("file: ") try: name = self.dest["filename"] except: name = "" self.fileNameBox = QLineEdit() self.fileNameBox.setText(name) self.destLayout.addWidget(fileLabel) self.destLayout.addWidget(self.fileNameBox) ## # \brief Function to draw the object def draw(self): #Add a submitDest Button selectDestButton = QPushButton("Select") selectDestButton.currentIndexChanged.connect( lambda: self.destChangeCallback()) self.destLayout.addWidget(destTitle) self.destLayout.addWidget(self.destCombo) self.destLayout.addWidget(selectDestButton) self.destLayout.addLayout(self.destLayout) ## # \brief callback for the Cancel button # def cancelButtonCallback(self): self.close() ## # \brief callback for a save button press # def saveButtonCallback(self): print("Saving:" + str(self.dest)) if self.dest["type"] == "console": print() print("Schema (" + str(time.time()) + ")") print(str(self.schema)) elif self.dest["type"] == "file": with open(self.dest["filename"], 'w') as outfile: json.dump(self.schema, outfile) else: print("Source type: " + str(self.dest["type"]) + " is not currently supported") self.close()
class ProjectsWindow(QMdiSubWindow): def __init__(self, app, OAuth_token, parent): super().__init__() self.app = app self.token = OAuth_token self.parent = parent self.open_windows = self.parent.open_windows self.initFig() self.initUI() def initFig(self): """ Initialize Figshare information """ self.project_list = self.get_project_list(self.token) def initUI(self): self.format_window() # Create a horizontal box layout to hold the project buttons self.project_buttons_box = QHBoxLayout() # Create a vertical box layout to hold the project window widgets and layouts self.vbox = QVBoxLayout() # Add the Projects button to the vertical box layout init_finish = len(self.project_list) if init_finish > 4: init_finish = 4 self.create_project_bar(0, init_finish) self.vbox.addLayout(self.project_buttons_box) # Add the scroll bar to the vertical box layout self.s_bar = self.scroll_bar() self.vbox.addWidget(self.s_bar) self.hbox = QHBoxLayout() temp = QVBoxLayout() temp.addWidget(self.search_bar()) temp.addLayout(self.management_buttons()) self.hbox.addLayout(temp) self.hbox.addLayout(self.vbox) # Create a central widget for the projects window window_widget = QWidget() # Add the vertical box layout window_widget.setLayout(self.hbox) # Set the projects window widget self.setWidget(window_widget) ##### # Window Formatting ##### def format_window(self): """ Formats the Projects window """ # Gets the QRect of the main window geom = self.parent.geometry() # Gets the Qrect of the sections window section_geom = self.parent.section_geom # Define geometries for the projects window x0 = section_geom.x() + section_geom.width() y0 = section_geom.y() w = geom.width() - x0 h = ((geom.height() - y0) / 6) self.setGeometry(x0, y0, w, h) # Remove frame from projects window self.setWindowFlags(Qt.FramelessWindowHint) ##### # Window Widgets ##### def scroll_bar(self): """ Creates a scroll bar set to the size of the projects list :return: QScrollBar Object """ s_bar = QScrollBar(Qt.Horizontal) s_bar.setMaximum(len(self.project_list) - 4) s_bar.sliderMoved.connect(self.slider_val) s_bar.valueChanged.connect(self.slider_val) return s_bar def create_proj_thumb(self, title, published_date, project_id): """ Creates a large pushbutton for a project :param title: string. Project title :param published_date: string. project published date :param id: int. figshare project id number :return: QPushButton object """ geom = self.geometry() # Get the scalig ratios for the current window w_ratio, f_ratio = scaling_ratio(self.app) # Scale the font sizes title_fnt_size = 12 * f_ratio date_ftn_size = 8 * f_ratio # Create the title label title_lbl = QLabel() title_lbl.setText("{}".format(title)) title_lbl_fnt = QFont('SansSerif', title_fnt_size) title_lbl_fnt.setBold(True) title_lbl.setFont(title_lbl_fnt) title_lbl.setWordWrap(True) # Create the date label date_lbl = QLabel() if published_date is None: published_date = 'Private' date_lbl.setText("Published: {}".format(published_date)) date_lbl_fnt = QFont('SansSerif', date_ftn_size) date_lbl.setFont(date_lbl_fnt) date_lbl.setStyleSheet('color: gray') date_lbl.setWordWrap(True) # Create a layout to hold the labels lbl_box = QVBoxLayout() # Add labels to layout lbl_box.addWidget(title_lbl) lbl_box.addWidget(date_lbl) # Create a button for the project btn = QPushButton(self) checkable_button(self.app, btn) btn.setLayout(lbl_box) btn.clicked[bool].connect(lambda: self.on_project_pressed(project_id)) self.project_buttons_box.addWidget(btn) def create_project_bar(self, start, finish): """ Creates a series of Project push buttons :param start: start position in projects list :param finish: finish position in projects list """ self.buttons = {} i = 0 for project_pos in range(start, finish): title = self.project_list[project_pos]['title'] pub_date = self.project_list[project_pos]['published_date'] project_id = self.project_list[project_pos]['id'] self.create_proj_thumb(title, pub_date, project_id) self.buttons[project_id] = self.project_buttons_box.itemAt( i).widget() i += 1 def management_buttons(self): """ Creates a layout that holds buttons to be used for creating and deleting projects :return: QVBoxLayout holding the create, and delete projects buttons """ # Create New Project Button np_btn = QPushButton() np_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) np_btn.setIcon( QIcon(os.path.abspath(__file__ + '/../..' + '/img/Folder-48.png'))) np_btn.setToolTip('Create a new Figshare Project') np_btn.setToolTipDuration(1) np_btn.pressed.connect(self.on_projects_btn_pressed) # Create Delete Project Button del_btn = QPushButton() del_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding) del_btn.setIcon( QIcon(os.path.abspath(__file__ + '/../..' + '/img/del_folder.png'))) del_btn.setToolTip('Delete Selected Project') del_btn.setToolTipDuration(1) del_btn.pressed.connect(self.on_delete_btn_pressed) # Create layout to hold buttons hbox = QHBoxLayout() # Add Buttons to layout hbox.addWidget(np_btn) hbox.addWidget(del_btn) return hbox def search_bar(self): """ Creates a QLineEdit object for the user to enter a search query :return: Edits the projects list object according to the filter """ # Create text box edit = QLineEdit() # Set font style search_bar(self.app, edit) # Set place holder text edit.setPlaceholderText('Search') # Add a clear button to the line edit edit.setClearButtonEnabled(True) # Add mouse over text edit.setToolTip('Search for specific Figshare Projects') edit.setToolTipDuration(1) # Connect search function to the return key edit.returnPressed.connect(lambda: self.search_on_return(edit.text())) edit.textChanged.connect(lambda: self.search_on_clear(edit.text())) return edit ##### # Widget Actions ##### def slider_val(self): """ Called when the projects button slider is changed. Removes all existing buttons and regenerates from the new position :return: """ while self.project_buttons_box.count(): item = self.project_buttons_box.takeAt(0) item.widget().deleteLater() s_bar_pos = self.s_bar.value() if 1 < len(self.project_list) < 4: number = len(self.project_list) else: number = 4 self.s_bar.setMaximum(len(self.project_list) - number) self.create_project_bar(s_bar_pos, s_bar_pos + number) def search_init(self): """ Called when the projects search bar is used. Removes all existing buttons and regenerates from new projects list :return: """ while self.project_buttons_box.count(): item = self.project_buttons_box.takeAt(0) item.widget().deleteLater() if 1 <= len(self.project_list) <= 4: number = len(self.project_list) else: number = 4 self.s_bar.setMaximum(len(self.project_list) - number) self.create_project_bar(0, number) def search_on_return(self, search_text): """ Called when return is pressed in the search bar. :return: """ if search_text != '': self.project_list = self.search_projects(search_text, self.token) self.search_init() def search_on_clear(self, lineedit_text): """ Called when the search bar is cleared :return: """ if lineedit_text == '': self.project_list = self.get_project_list(self.token) self.slider_val() def on_projects_btn_pressed(self): """ Called when the create new project button is pressed """ if 'new_project_window' in self.open_windows: self.open_windows.remove('new_project_window') self.parent.new_project_window.close() else: self.open_windows.remove('projects_window') self.close() if 'project_info_window' in self.open_windows: self.parent.project_info_window.close() self.open_windows.remove('project_info_window') if 'project_articles_window' in self.open_windows: self.parent.project_articles_window.close() self.open_windows.remove('project_articles_window') if 'article_edit_window' in self.open_windows: self.open_windows.remove('article_edit_window') self.parent.article_edit_window.close() self.open_windows.add('new_project_window') self.parent.new_project_window = NewProjectWindow( self.app, self.token, self.parent) self.parent.mdi.addSubWindow(self.parent.new_project_window) self.parent.new_project_window.show() def on_project_pressed(self, project_id): """ Called when a project is clicked. :return: """ # For if there is already a project info window open if 'project_info_window' in self.open_windows: # Get the project id number of the current window open_proj = self.parent.project_info_window.project_id # For a different project than the currently open project if open_proj != project_id: # If the current project is in the current view of project buttons (it may have been scrolled away from) if open_proj in self.buttons: # If that button is checked, uncheck it if self.buttons[open_proj].isChecked(): self.buttons[open_proj].toggle() # Close the currently open project info window self.parent.project_info_window.close() # Create a new project info window for the different project self.parent.project_info_window = ProjectInfoWindow( self.app, self.token, self.parent, project_id) # Add it as a sub window to the framing window self.parent.mdi.addSubWindow(self.parent.project_info_window) self.parent.project_info_window.show() # If the current projects button is pressed else: # Close the window and remove from the open window list self.open_windows.remove('project_info_window') self.parent.project_info_window.close() # If any sub windows are open close them as well if 'project_articles_window' in self.open_windows: self.open_windows.remove('project_articles_window') self.parent.project_articles_window.close() if 'article_edit_window' in self.open_windows: self.open_windows.remove('article_edit_window') self.parent.article_edit_window.close() # For when no project info window is open else: self.open_windows.add('project_info_window') self.parent.project_info_window = ProjectInfoWindow( self.app, self.token, self.parent, project_id) self.parent.mdi.addSubWindow(self.parent.project_info_window) self.parent.project_info_window.show() def on_delete_btn_pressed(self): """ Called when the project delete button is pressed/ :return: """ open_proj = self.parent.project_info_window.project_id project_title = self.parent.project_info_window.project_info['title'] msg = "Are you sure you want to delete Figshare Project: {}".format( project_title) msg_box = QMessageBox.question(self, 'Message', msg, QMessageBox.Yes, QMessageBox.No) if msg_box == QMessageBox.Yes: successful = self.delete_project(self.token, open_proj) if successful: con_reply = QMessageBox.information( self, 'Deletion Confirmation', 'Project successfully deleted', QMessageBox.Ok) if con_reply == QMessageBox.Ok: self.reopen_projects() else: self.reopen_projects() else: con_reply = QMessageBox.warning( self, 'Deletion Confirmation', 'Unknown error occurred.\n Project may not have been deleted.', QMessageBox.Ok) if con_reply == QMessageBox.Ok: self.reopen_projects() else: self.reopen_projects() def reopen_projects(self): """ Called to open and close the projects window. :return: """ for i in range(2): self.parent.section_window.on_projects_btn_pressed() ##### # Figshare API Interface Calls ##### def get_project_list(self, token): """ Returns the users private project list :param token: Figshare OAuth token :return: array of project """ projects = Projects(token) return projects.get_list() def search_projects(self, search_text, token): """ Returns a list of projects matching the users search criteria :param search_text: String. Figshare style elastic search string :param token: Figshare OAuth token :return: """ projects = Projects(token) result = projects.search(search_text) if len(result) == 0: result = projects.get_list() return result def delete_project(self, token, project_id): """ Deletes the given project from Figshare :param token: :param project_id: Int. Figshare project ID number :return: """ projects = Projects(token) try: projects.delete( project_id, safe=False ) # Suppresses command line requirement for acknowledgement return True except: return False
class OptionsBar(QWidget): def __init__(self, task_widget): super().__init__(task_widget) self.task_widget = task_widget self.area = None self.options = [] self.v_options = [] self.buttons = [] self.layout = QHBoxLayout() self.layout.setSizeConstraint(QLayout.SetFixedSize) self.setLayout(self.layout) def with_area(self): self.area = AreaOptions() self.layout.addLayout(self.area) return self def with_int_options_v(self, top=1, bottom=10, top_name='α', bottom_name='β', min=-10000): v_option = VerticalIntOption(top, bottom, top_name, bottom_name, min) self.v_options.append(v_option) self.layout.addLayout(v_option) return self def with_int_option(self, name, default=0, min=-10000, max=10000): option = IntOption(name, default, min, max) self.options.append(option) self.layout.addLayout(option) return self def with_button(self, name, callback): button = QPushButton(name, self) button.clicked.connect(callback) self.buttons.append(button) self.layout.addWidget(button) return self def with_image(self, path): pixmap = QPixmap(path) label = QLabel() label.setPixmap(pixmap) self.layout.addWidget(label) return self def clear(self): self.options = [] self.v_options = [] self.buttons = [] for i in range(self.layout.count() - 1, -1, -1): item = self.layout.takeAt(i) self.layout.removeItem(item)
class QueryRowWgt(QWidget): valueTypeChanged = pyqtSignal(object) ontoMng = OntoManager() treeData = ontoMng.trees dicData = ontoMng.dics tagNames = np.array(list(dicData.values())) del dicData del treeData def __init__(self, searchType, parent=None): super().__init__(parent) self.searchType = searchType self.valueType = QComboBox(self) self.valueType.addItem("") if self.searchType == "Annotation": self.valueType.addItems(annotationKeys) elif self.searchType == "Parameter": self.valueType.addItems(parameterKeys) else: raise ValueError() self.valueType.currentIndexChanged.connect(self.valueTypeChangedEmit) self.value = QLineEdit(self) self.value.setEnabled(False) self.layout = QHBoxLayout(self) self.layout.addWidget(self.valueType) self.layout.addWidget(self.value) def valueTypeChangedEmit(self): self.value.setEnabled(self.valueType.currentText() != "") child = self.layout.takeAt(1) child.widget().deleteLater() if self.valueType.currentText() == "Has parameter": self.value = QComboBox(self) self.value.addItems(["False", "True"]) elif self.valueType.currentText() == "Annotation type": self.value = QComboBox(self) self.value.addItems( ["text", "figure", "table", "equation", "position"]) elif self.valueType.currentText() == "Parameter name": self.value = ParamTypeCbo(self) elif self.valueType.currentText() in ["Tag name", "Required tag name"]: self.value = AutoCompleteEdit(self) self.value.setModel(QueryRowWgt.tagNames) elif self.valueType.currentText() == "Result type": self.value = QComboBox(self) self.value.addItems(["pointValue", "function", "numericalTrace"]) else: self.value = QLineEdit(self) self.layout.addWidget(self.value) self.valueTypeChanged.emit(self) def getQuery(self): if self.valueType.currentText() == "": return None if isinstance(self.value, QLineEdit): value = self.value.text() elif isinstance(self.value, QComboBox): value = self.value.currentText() else: raise TypeError return ConditionAtom(self.valueType.currentText(), value)
class SpaceLineEdit(QWidget): SPLIT_CHARS = ' ,;' def __init__(self, parent=None, validator=None, flags=Qt.WindowFlags()): # type: (QWidget, QValidator, Qt.WindowFlags) -> None super(SpaceLineEdit, self).__init__(parent, flags) self.setFocusPolicy(Qt.StrongFocus) self._validator = validator self._layout = QHBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._prepareLineEdit(0) def setValidator(self, validator): # type: (QValidator) -> None self._validator = validator def getValues(self): # type: () -> List[str] values = [] for index in range(self._layout.count()): text = self._layout.itemAt(index).widget().text() values.append(text) return values def setValues(self, values): # type: (List[str]) -> None self._cleanAll() if not values: values = [''] for index, value in enumerate(values): self._prepareLineEdit(index, value) def _cleanAll(self): for index in reversed(range(self._layout.count())): widget = self._layout.takeAt(index).widget() # type: QWidget widget.setParent(None) widget.deleteLater() def _prepareLineEdit(self, position, text=''): # type: (int, str) -> None lineEdit = QLineEdit(str(text), self) lineEdit.setValidator(self._validator) lineEdit.textChanged.connect(partial(self.onTextChanged, lineEdit)) lineEdit.installEventFilter(self) self._layout.insertWidget(position, lineEdit) def focusInEvent(self, event): item = self._layout.itemAt(0) if item: item.widget().setFocus() def onTextChanged(self, lineEdit, text): # type: (QLineEdit, str) -> None if not text: self._removeLineEdit(lineEdit) text = self._convert(text) if self._isSplit(text): self._addLineEdit(lineEdit, text) def _removeLineEdit(self, lineEdit, changeFocus=True): # type: (QLineEdit, bool) -> None if self._layout.count() <= 1: return if changeFocus: if not self._setFocus(lineEdit, offset=-1): self._setFocus(lineEdit, offset=1) self._layout.removeWidget(lineEdit) lineEdit.deleteLater() @staticmethod def _convert(text): # type: (str) -> str for sch in SpaceLineEdit.SPLIT_CHARS[1:]: text = text.replace(sch, ' ') return text @staticmethod def _isSplit(text): # type: (str) -> bool return ' ' in text def _addLineEdit(self, lineEdit, text): # type: (QLineEdit, str) -> None texts = text.split() if not texts: lineEdit.setText('') return else: lineEdit.setText(texts[0]) if len(texts) == 1: texts.append('') insertIndex = self._layout.indexOf(lineEdit) + 1 for index, text in enumerate(texts[1:], insertIndex): self._prepareLineEdit(index, text) self._setFocus(lineEdit, offset=1) def eventFilter(self, watched, event): # type: (QWidget, QEvent) -> bool if isinstance(watched, QLineEdit): lineEdit = watched # type: QLineEdit if event.type() == QEvent.FocusOut and not lineEdit.text(): self._removeLineEdit(lineEdit, changeFocus=False) elif event.type() == QEvent.KeyPress and isinstance( event, QKeyEvent): if event.key() == Qt.Key_Backspace and not lineEdit.text(): self._removeLineEdit(lineEdit) if event.key() == Qt.Key_Down: self._setFocus(lineEdit, offset=1) elif event.key() == Qt.Key_Up: self._setFocus(lineEdit, offset=-1) return super(SpaceLineEdit, self).eventFilter(watched, event) def _setFocus(self, lineEdit, offset): # type: (QLineEdit, int) -> bool index = self._layout.indexOf(lineEdit) + offset item = self._layout.itemAt(index) if item: item.widget().setFocus(Qt.OtherFocusReason) return True return False
class Weijiejue(QDialog): def __init__(self,list,i,parent = None): super(Weijiejue,self).__init__(parent) #设置界面大小、名称、背景 #self.resize(800,900) self.setWindowTitle('故障诊断专家系统') self.setStyleSheet("background-image:url(tubiao_meitu.jpg)") QApplication.setStyle("Fusion") font = QtGui.QFont('微软雅黑',20) font1 = QtGui.QFont('微软雅黑light', 15) #窗体属性 self.i1=i self.list1=list # #查询self.list1[self.i1],得到cankaoziliao字符串,转换为列表 db = pymysql.connect("localhost", "root", "123456", "expertsy", charset='utf8') cur=db.cursor() #self.ckzl= ["手册1.pdf","图片1.jpg"] vbj = QVBoxLayout() vbj.setAlignment(Qt.AlignCenter) vbj.setSpacing(50) self.hbj1=QHBoxLayout() hbj2=QHBoxLayout() # self.setWindowFlags(Qt.Widget) self.paichatishi = QLabel(self.list1[self.i1]) self.paichatishi.setFont(font) sql = 'SELECT 参考资料 FROM 问题关联 WHERE 问题="%s"' % self.list1[self.i1] cur.execute(sql) string = cur.fetchone() print(string) if len(string)!=0: string0 = string[0] self.ckzl = string0.split(",") for fuzu in self.ckzl: self.title1= QPushButton(fuzu) self.title1.setStyleSheet(''' QPushButton{ border:none; color:blue; font-size:15px; height:30px; padding-left:5px; padding-right:5px; text-align:center; } QPushButton:hover{ color:black; border:1px solid #F3F3F5; border-radius:10px; background:LightGray; } ''') file_path= fuzu self.title1.clicked.connect(partial(self.lianjie)) #title1.clicked.connect(lambda :self.lianjie(file_path)) self.hbj1.addWidget(self.title1) self.jiejuebut=QPushButton("解决了") self.jiejuebut.setFont(font1) self.weijiejuebut = QPushButton("未解决") self.weijiejuebut.setFont(font1) self.weijiejuebut.clicked.connect(partial(self.tonext)) self.jiejuebut.clicked.connect(partial(self.showjiejue)) hbj2.addStretch(1) hbj2.addWidget(self.weijiejuebut) hbj2.addStretch(2) hbj2.addWidget(self.jiejuebut) hbj2.addStretch(1) vbj.addStretch(1) vbj.addWidget(self.paichatishi) vbj.addStretch(2) vbj.addLayout(self.hbj1) vbj.addStretch(2) vbj.addLayout(hbj2) vbj.addStretch(1) self.setLayout(vbj) def tonext(self): for i in reversed (range(self.hbj1.count())): self.hbj1.itemAt(i).widget().close() self.hbj1.takeAt(i) changdu=len(self.list1) self.i1=self.i1+1 if self.i1+1<changdu: self.paichatishi.setText(self.list1[self.i1]) if self.i1+1==changdu: self.paichatishi.setText(self.list1[self.i1]) self.weijiejuebut.setText("已经是最后一条") self.weijiejuebut.setEnabled(False) db = pymysql.connect("localhost", "root", "123456", "expertsy", charset='utf8') cur = db.cursor() sql = 'SELECT 参考资料 FROM 问题关联 WHERE 问题="%s"' % self.list1[self.i1] cur.execute(sql) string = cur.fetchone() string0 = string[0] if not string0 is None: print("assa") self.ckzl = string0.split(",") for fuzu in self.ckzl: self.title1= QPushButton(fuzu) self.title1.setStyleSheet(''' QPushButton{ border:none; color:blue; font-size:15px; height:30px; padding-left:5px; padding-right:5px; text-align:center; } QPushButton:hover{ color:black; border:1px solid #F3F3F5; border-radius:10px; background:LightGray; } ''') self.title1.clicked.connect(partial(self.lianjie)) #title1.clicked.connect(lambda :self.lianjie(file_path)) self.hbj1.addWidget(self.title1) def showjiejue(self): self.w3=Jiejue() self.w3.show() def lianjie(self): sender=self.sender() os.startfile(sender.text())
class TrialConfigDialog(QDialog): def __init__(self, config: TrialConfigurations = TrialConfigurations()): super().__init__() self.config = config self.available_classes = self.config.classes.copy() self.setWindowTitle("Edit Trial Configurations") self.root_layout = QGridLayout() self.setLayout(self.root_layout) self.root_layout.setAlignment(PyQt5.QtCore.Qt.AlignTop | PyQt5.QtCore.Qt.AlignLeft) # Add title title = QLabel("<h1>Edit Trial Configurations</h1>") title.setAlignment(PyQt5.QtCore.Qt.AlignCenter) self.root_layout.addWidget(title, 0, 0, 1, 3) # Start delay edit self.start_delay_edit = QLineEdit() self.start_delay_edit.setText(str(self.config.start_delay)) self.start_delay_edit.textChanged.connect( self.update_total_trial_duration_label) # Update info label self.root_layout.addWidget( self.label_widgets_row("Start Delay (sec): ", [self.start_delay_edit]), 1, 0, 1, 1) # Trial duration edit self.trial_duration_edit = QLineEdit() self.trial_duration_edit.setText(str(self.config.trial_duration)) self.trial_duration_edit.textChanged.connect( self.update_total_trial_duration_label) # Update info label self.root_layout.addWidget( self.label_widgets_row("Trial Duration (sec): ", [self.trial_duration_edit]), 1, 1, 1, 1) # Repetitions edit self.repetitions_edit = QLineEdit() self.repetitions_edit.setText(str(self.config.repetitions)) self.repetitions_edit.textChanged.connect( self.update_total_trial_duration_label) # Update info label self.root_layout.addWidget( self.label_widgets_row("Repetitions: ", [self.repetitions_edit]), 2, 0, 1, 1) # Relaxation period self.relaxation_period_edit = QLineEdit() self.relaxation_period_edit.setText(str(self.config.relaxation_period)) self.relaxation_period_edit.textChanged.connect( self.update_total_trial_duration_label) # Update info label self.root_layout.addWidget( self.label_widgets_row("Relaxation Period (sec): ", [self.relaxation_period_edit]), 2, 1, 1, 1) # Class count self.class_count_slider = QSlider() self.class_count_slider.setOrientation(PyQt5.QtCore.Qt.Horizontal) self.class_count_slider.setRange(2, len(self.available_classes)) self.class_count_slider.setValue(len(self.config.classes)) self.class_count_slider.valueChanged.connect(self.class_count_update) self.root_layout.addWidget( self.label_widgets_row("Number of Classes: ", [self.class_count_slider]), 3, 0, 1, 1) self.class_picker_widget = QWidget() self.class_picker_layout = QHBoxLayout() self.class_picker_widget.setLayout(self.class_picker_layout) self.classes_combo_boxes = [] self.root_layout.addWidget(self.class_picker_widget, 3, 1, 1, 1) self.class_count_update() # Vibration control self.vibration_control_checkbox = QCheckBox("Vibration Control") self.vibration_control_checkbox.setChecked( self.config.vibration_control) self.left_freq_edit = QLineEdit() self.left_freq_edit.setText(str(self.config.left_frequency)) self.right_freq_edit = QLineEdit() self.right_freq_edit.setText(str(self.config.right_frequency)) self.root_layout.addWidget( utils.construct_horizontal_box([ self.vibration_control_checkbox, QLabel("Left Frequency (Hz):"), self.left_freq_edit, QLabel("Right Frequency (Hz):"), self.right_freq_edit ]), 4, 0, 1, 3) # File saving settings second_title = QLabel("<h2>Save Files</h2>") second_title.setAlignment(PyQt5.QtCore.Qt.AlignCenter) self.root_layout.addWidget(second_title, 5, 0, 1, 3) self.root_directory_path_label = QLabel(self.config.root_directory) self.change_root_directory_btn = QPushButton("Select/Change") self.change_root_directory_btn.clicked.connect( self.change_root_directory_clicked) self.root_layout.addWidget( self.label_widgets_row("Root Directory: ", [ self.root_directory_path_label, self.change_root_directory_btn ]), 6, 0, 1, 3) info_title = QLabel("<h3>Info</h3>") info_title.setAlignment(PyQt5.QtCore.Qt.AlignCenter) self.root_layout.addWidget(info_title, 7, 0, 1, 3) self.total_trial_duration_label = QLabel() self.total_trial_duration_label.setAlignment( PyQt5.QtCore.Qt.AlignCenter) self.root_layout.addWidget(self.total_trial_duration_label, 8, 0, 1, 3) self.trials_for_class_label = QLabel() self.trials_for_class_label.setAlignment(PyQt5.QtCore.Qt.AlignCenter) self.root_layout.addWidget(self.trials_for_class_label, 9, 0, 1, 3) self.update_total_trial_duration_label() self.update_trials_for_class_label() self.save_btn = QPushButton("Save") self.save_btn.clicked.connect(self.save) self.cancel_btn = QPushButton("Cancel") self.cancel_btn.clicked.connect(self.cancel) self.root_layout.addWidget(self.save_btn, 10, 0, 1, 1) self.root_layout.addWidget(self.cancel_btn, 10, 1, 1, 1) def obtain_info_from_line_edits(self): if utils.is_integer(self.start_delay_edit.text()): self.config.start_delay = int(self.start_delay_edit.text()) if utils.is_integer(self.trial_duration_edit.text()): self.config.trial_duration = int(self.trial_duration_edit.text()) if utils.is_integer(self.repetitions_edit.text()): self.config.repetitions = int(self.repetitions_edit.text()) if utils.is_integer(self.relaxation_period_edit.text()): self.config.relaxation_period = int( self.relaxation_period_edit.text()) if utils.is_integer(self.left_freq_edit.text()): self.config.left_frequency = int(self.left_freq_edit.text()) if utils.is_integer(self.right_freq_edit.text()): self.config.right_frequency = int(self.right_freq_edit.text()) self.config.vibration_control = self.vibration_control_checkbox.isChecked( ) def save(self): print("Save clicked") self.obtain_info_from_line_edits() class_count = len(self.classes_combo_boxes) self.config.classes.clear() for i in range(class_count): class_name = self.classes_combo_boxes[i].currentText() for available_class in self.available_classes: if available_class.name == class_name: print("Adding class named {}".format(available_class.name)) self.config.classes.append(available_class) self.config.root_directory = self.root_directory_path_label.text() if self.config.root_directory == "": print("Please select a root directory") return content = listdir(self.config.root_directory) if global_config.EEG_DATA_FILE_NAME in content or global_config.SLICE_INDEX_FILE_NAME in content: print("Directory contains data from a previous trial...") msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText( "The selected directory contains data from a previous trial.\n" ) msg.setInformativeText( "You can choose to override the existing data or append to it." ) msg.setWindowTitle("Warning") msg.setDetailedText( "Do you want to overwrite the files in the selected directory or append the data to them?" ) overwrite_btn = QPushButton("Overwrite Files") msg.addButton(overwrite_btn, QMessageBox.DestructiveRole) append_btn = QPushButton("Append To Files") msg.addButton(append_btn, QMessageBox.YesRole) cancel_btn = QPushButton("Cancel") msg.addButton(cancel_btn, QMessageBox.RejectRole) # msg.setDefaultButton(append_btn) msg.exec() if msg.clickedButton() == cancel_btn: return elif msg.clickedButton() == overwrite_btn: self.config.overwrite_files = True elif msg.clickedButton() == append_btn: self.config.overwrite_files = False self.config.non_empty_root_directory = True print("Saving and closing, overwrite? {}".format( self.config.overwrite_files)) self.close() else: print("Saving and closing") self.close() def cancel(self): self.close() def update_total_trial_duration_label(self): previous_repetitions = self.config.repetitions self.obtain_info_from_line_edits() if previous_repetitions != self.config.repetitions: self.update_trials_for_class_label() duration = self.config.start_delay + \ self.config.repetitions * (self.config.trial_duration + self.config.relaxation_period) minutes = duration // 60 seconds = duration - minutes * 60 if minutes == 1: self.total_trial_duration_label.setText( "<h3>Estimated time to complete trial: {} minute and {} seconds</h3>" .format(minutes, seconds)) else: self.total_trial_duration_label.setText( "<h3>Estimated time to complete trial: {} minutes and {} seconds</h3>" .format(minutes, seconds)) def update_trials_for_class_label(self): repetitions = self.config.repetitions class_count = self.class_count_slider.value() try: self.trials_for_class_label.setText( "<h3>Expected trials for each class: {}</h3>".format( 1 / class_count * repetitions)) except AttributeError: pass def class_count_update(self): class_count = self.class_count_slider.value() if len(self.classes_combo_boxes) != 0: self.clear_class_picker_layout() self.classes_combo_boxes.clear() for i in range(class_count): self.classes_combo_boxes.append(self.create_class_combo_box()) if i < len(self.available_classes): self.classes_combo_boxes[-1].setCurrentText( self.available_classes[i].name) self.class_picker_layout.addWidget( self.label_widgets_row(str(self.available_classes[i].label), [self.classes_combo_boxes[-1]])) self.update_trials_for_class_label() def change_root_directory_clicked(self): path_to_directory = QFileDialog.getExistingDirectory( self, "Root Directory") self.root_directory_path_label.setText(path_to_directory) def create_class_combo_box(self): combo_box = QComboBox() for i in range(len(self.available_classes)): combo_box.addItem(self.available_classes[i].name) return combo_box def clear_class_picker_layout(self): while self.class_picker_layout.count() > 0: child = self.class_picker_layout.takeAt(0) if child.widget(): child.widget().deleteLater() @staticmethod def label_widgets_row(label_text: str, widgets: [QWidget]): layout = QHBoxLayout() layout.setAlignment(PyQt5.QtCore.Qt.AlignCenter) widget = QWidget() widget.setLayout(layout) layout.addWidget(QLabel(label_text)) for item in widgets: layout.addWidget(item) return widget def edit_config(self) -> TrialConfigurations: self.show() self.exec() return self.config