示例#1
0
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)
示例#2
0
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()
示例#3
0
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)
示例#4
0
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)
示例#6
0
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()
示例#7
0
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()
示例#8
0
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()
示例#9
0
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):
        """
示例#10
0
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
示例#11
0
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()
示例#12
0
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
示例#13
0
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()
示例#14
0
文件: myojo.py 项目: chao7150/myojo
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)
示例#15
0
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()
示例#16
0
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
示例#17
0
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)
示例#18
0
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)
示例#19
0
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())
示例#21
0
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