Exemple #1
0
class SignalGrouper(QDialog):
    def __init__(self, chartData, parent=None):
        QDialog.__init__(self, parent)
        self.chartData = chartData
        self._create()

    def _create(self):
        self.mLayout = QVBoxLayout(self)
        
        self.gSelector = QListWidget()
        groups = self.chartData.getDataGroups()
        self.gSelector.addItems(groups)
        self.gSelector.currentTextChanged.connect(self._updateGroupList)
        self.chartData.dataGroupAdded.connect(self.gSelector.addItem)
        self.chartData.dataGroupAdded.connect(self.gSelector.addItem)
        self.mLayout.addWidget(self.gSelector)

        self.sSelector = QListWidget()
        self.mLayout.addWidget(self.sSelector)

        groupBtn = QPushButton('Create group from selected')
        groupBtn.clicked.connect(self.createNewGroup)
        self.mLayout.addWidget(groupBtn)
        

    def _updateGroupList(self):
        newGroup = self.gSelector.currentItem().text()
        sStruct = self.chartData[newGroup].getColStructure()
        self.sSelector.clear()
        for ws in sStruct:
            firstChannel = sStruct[ws][0]
            isOneSignal = self.chartData[newGroup][firstChannel][ws][0]
            if isOneSignal:
                item = QListWidgetItem(ws, self.sSelector)
                item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
                item.setCheckState(Qt.Unchecked)

    def createNewGroup(self):
        checkList = []
        for i in range(self.sSelector.count()):
            sItem = self.sSelector.item(i)
            if sItem.checkState() == Qt.Checked:
                checkList.append(sItem.text())
        if len(checkList) > 0:
            groupName, result = QInputDialog().getText(self, 'Input', 'Enter group name:')
            if result:
                ws = self.gSelector.currentItem().text()
                sStruct = self.chartData[ws].getColStructure(checkList)
                sKeys = list(sStruct.keys())
                for s in range(len(sKeys)):
                    if sStruct[sKeys[s]] != sStruct[sKeys[s-1]]:
                        print('Signals have diffrent channel sets')
                        return
                self.chartData.appendSignalGroup(ws, groupName, sStruct[sKeys[0]], checkList)
            else:
                return
        else:
            return
Exemple #2
0
 def __init__(self):
     super().__init__()
     widget = QListWidget()
     
     widget.addItems(["First", "Second", "Third"])
     widget.currentItemChanged.connect(self.item_changed)
     widget.currentTextChanged.connect(self.text_changed)
     
     self.setCentralWidget(widget)
Exemple #3
0
class StaticCollection(basicCollection.BasicCollection):
    """
        This class represents the property editor view of a static collection.
        A static selection is read-only and only contains a list of selected nodes.
    """

    # Constants
    LIST_BOX_HEIGHT = utils.dpiScale(100)

    def __init__(self, item, parent):
        super(StaticCollection, self).__init__(item, parent)
        self.addToCollectionGroupBoxLayout = None
        self.staticSelector = None

    def setupSelector(self, layout):
        self._setupAddToCollectionGroupBox(layout)

    def _setupStaticSelector(self):
        staticSelectionWidget = QWidget()
        staticSelectionLayout = QHBoxLayout()
        staticSelectionLayout.setContentsMargins(0, 0, 0, 0)
        staticSelectionLayout.setSpacing(utils.dpiScale(2))
        self.staticSelector = QListWidget()
        self.staticSelector.setFixedHeight(self.LIST_BOX_HEIGHT)
        staticSelectionLayout.addWidget(self.staticSelector)

        # Re-populate the static selection list view with the previously stored value
        staticSelections = self.item().model.getSelector().getStaticSelection()
        staticSelectionList = staticSelections.split(
        ) if staticSelections is not None else list()
        self.staticSelector.addItems(staticSelectionList)

        # Add the drag/drop buttons
        dragDropButtonLayout = QVBoxLayout()
        dragDropButtonLayout.setSpacing(utils.dpiScale(2))
        dragDropButtonLayout.setContentsMargins(0, 0, 0, 0)
        dragDropButtonWidget = QWidget()
        staticSelectionLayout.addWidget(dragDropButtonWidget)
        dragDropButtonWidget.setLayout(dragDropButtonLayout)
        staticSelectionWidget.setLayout(staticSelectionLayout)
        self.addToCollectionGroupBoxLayout.addRow("", staticSelectionWidget)

    def _setupAddToCollectionGroupBox(self, layout):
        addToCollectionGroupBox = QGroupBox(
            maya.stringTable['y_staticCollection.kAddToCollection'])
        font = QFont()
        font.setBold(True)
        addToCollectionGroupBox.setFont(font)
        addToCollectionGroupBox.setContentsMargins(0, utils.dpiScale(12), 0, 0)
        self.addToCollectionGroupBoxLayout = propEdLayout.Layout()
        self.addToCollectionGroupBoxLayout.setVerticalSpacing(
            utils.dpiScale(2))

        self._setupStaticSelector()

        addToCollectionGroupBox.setLayout(self.addToCollectionGroupBoxLayout)
        layout.addWidget(addToCollectionGroupBox)
Exemple #4
0
    def __init__(self):

        super().__init__()

        # ─────────────────── Check boxes ────────────────── #

        self.code_mode_checkbox = QCheckBox('L∃∀N mode')
        checkbox_lyt = QHBoxLayout()
        checkbox_lyt.addStretch()
        checkbox_lyt.addWidget(self.code_mode_checkbox)

        self.code_mode_checkbox.clicked.connect(self.toggle_code_mode)

        # ───────────────── Friendly widget ──────────────── #

        self.friendly_wgt = QWidget()
        friendly_wgt_lyt = QVBoxLayout()
        friendly_wgt_lyt.setContentsMargins(0, 0, 0, 0)

        propobj_lyt = QHBoxLayout()
        objects, properties = QListWidget(), QListWidget()
        objects_lyt, properties_lyt = QVBoxLayout(), QVBoxLayout()
        objects_lyt.addWidget(QLabel('Objects:'))
        properties_lyt.addWidget(QLabel('Properties:'))
        objects.setFont(QFont('Fira Code'))
        properties.setFont(QFont('Fira Code'))
        objects.addItems(['X : a set', 'x : X'])
        properties.addItems(['X is compact'])
        objects_lyt.addWidget(objects)
        properties_lyt.addWidget(properties)
        propobj_lyt.addLayout(objects_lyt)
        propobj_lyt.addLayout(properties_lyt)

        target_wgt = QLineEdit('Shit f**k X is continuous over Riemann')
        target_wgt.setFont(QFont('Fira Code'))

        friendly_wgt_lyt.addLayout(propobj_lyt)
        friendly_wgt_lyt.addWidget(QLabel('Target:'))
        friendly_wgt_lyt.addWidget(target_wgt)

        self.friendly_wgt.setLayout(friendly_wgt_lyt)

        # ─────────────────── Code widget ────────────────── #

        self.code_wgt = QTextEdit()
        self.code_wgt.setReadOnly(True)
        self.code_wgt.setFont(QFont('Menlo'))
        self.code_wgt.setText('Lean code goes here')

        # ──────────────────── Organize ──────────────────── #
        self.code_mode_checkbox.setChecked(False)
        self.friendly_wgt.show()
        self.code_wgt.hide()

        self.addWidget(self.friendly_wgt)
        self.addWidget(self.code_wgt)
        self.addLayout(checkbox_lyt)
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Combo box")
        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        widget.currentItemChanged.connect(self.index_changed)
        widget.currentTextChanged.connect(self.text_changed)

        self.setCentralWidget(widget)
Exemple #6
0
 def addButtonExample(self):
     # 创建一些小部件放在顶级窗口中
     btn = QPushButton('press me')
     text = QLineEdit('enter text')
     listw = QListWidget()
     listw.addItems(["aa", "bb", "cc"])
     # self.gridLayout = QGridLayout(self)
     # 将部件添加到布局中的适当位置,
     # addWidget参数:Widget实例, 起始row, 起始column, 占多少行(高度),占多少列(宽度)
     self.gridLayout.addWidget(btn, 0, 0)
     self.gridLayout.addWidget(text, 1, 0)
     self.gridLayout.addWidget(listw, 2, 0)
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        # In QListWidget there are two separate signals for the item, and the str
        widget.currentItemChanged.connect(self.index_changed)
        widget.currentTextChanged.connect(self.text_changed)

        self.setCentralWidget(widget)
Exemple #8
0
    def get_connector(self, importee):
        """Shows a QDialog to select a connector for the given source file.
        Mimics similar routine in `spine_io.widgets.import_widget.ImportDialog`

        Args:
            importee (str): Label of the file acting as an importee

        Returns:
            Asynchronous data reader class for the given importee
        """
        connector_list = [
            CSVConnector, ExcelConnector, GdxConnector, JSONConnector
        ]  # add others as needed
        connector_names = [c.DISPLAY_NAME for c in connector_list]
        dialog = QDialog(self._toolbox)
        dialog.setLayout(QVBoxLayout())
        connector_list_wg = QListWidget()
        connector_list_wg.addItems(connector_names)
        # Set current item in `connector_list_wg` based on file extension
        _filename, file_extension = os.path.splitext(importee)
        file_extension = file_extension.lower()
        if file_extension.startswith(".xls"):
            row = connector_list.index(ExcelConnector)
        elif file_extension in (".csv", ".dat", ".txt"):
            row = connector_list.index(CSVConnector)
        elif file_extension == ".gdx":
            row = connector_list.index(GdxConnector)
        elif file_extension == ".json":
            row = connector_list.index(JSONConnector)
        else:
            row = None
        if row is not None:
            connector_list_wg.setCurrentRow(row)
        button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                      | QDialogButtonBox.Cancel)
        button_box.button(QDialogButtonBox.Ok).clicked.connect(dialog.accept)
        button_box.button(QDialogButtonBox.Cancel).clicked.connect(
            dialog.reject)
        connector_list_wg.doubleClicked.connect(dialog.accept)
        dialog.layout().addWidget(connector_list_wg)
        dialog.layout().addWidget(button_box)
        _dirname, filename = os.path.split(importee)
        dialog.setWindowTitle("Select connector for '{}'".format(filename))
        answer = dialog.exec_()
        if answer:
            row = connector_list_wg.currentIndex().row()
            return connector_list[row]
Exemple #9
0
    def set_docker(self):
        """Setup dock widget"""
        dock = self._window.dock_tool
        dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
                             | Qt.TopDockWidgetArea | Qt.BottomDockWidgetArea)
        data_list = QListWidget(dock)
        data_list.addItems([
            "John Doe, Harmony Enterprises, 12 Lakeside, Ambleton",
            "Jane Doe, Memorabilia, 23 Watersedge, Beaton",
            "Tammy Shea, Tiblanka, 38 Sea Views, Carlton",
            "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal",
            "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston",
            "Sally Hobart, Tiroli Tea, 67 Long River, Fedula"
        ])
        dock.setWidget(data_list)

        self._window.addDockWidget(Qt.RightDockWidgetArea, dock)
Exemple #10
0
class DatasheetView(QMainWindow):

    def __init__(self, pdfPath=None, pageNumber=1):
        
        super().__init__()

        # initialize data files
        # self.fileStore = path.join(path.curdir, "/files")
        # mkdir(self.fileStore)
        self.svgFiles = []


        # window dimensions
        self.top = 300
        self.left = 800
        self.width = 860
        self.height = 980

        self.setGeometry(self.left, self.top, self.width, self.height)
        
        # window title
        self.setWindowTitle("Hello")


        # sets up main layout -- splitters
        self.initUILayout()  
        self.initUIToolbar()

        self.populatePDF(pdfPath, pageNumber)

        self.show()


    def initUILayout(self):

        # set left-side, Dynamic View
        self.dynamicViewDisplay = QLabel()

        self.vBoxA = QVBoxLayout()
        self.vBoxA.addWidget(self.dynamicViewDisplay)
        
        self.groupA = QGroupBox()
        self.groupA.setTitle("Dynamic Veiw")
        self.groupA.setLayout(self.vBoxA)
        
        # set left-side, Static View
        self.staticViewDisplay = QLabel()
        
        self.vBoxB = QVBoxLayout()
        self.vBoxB.addWidget(self.staticViewDisplay)

        self.groupB = QGroupBox()
        self.groupB.setTitle("Static View")
        self.groupB.setLayout(self.vBoxB)


        # add Dynamic and Static Views to resizeable left-side Splitter
        self.altViewSplit = QSplitter(Qt.Vertical)
        self.altViewSplit.addWidget(self.groupA)
        self.altViewSplit.addWidget(self.groupB)
        


        # set up Tools View section
        self.toolsTabView = QTabWidget()
        self.toolsTabView.setTabsClosable(True)
        self.toolsTabView.setMovable(True)
        self.toolsTabView.setDocumentMode(True)

        # self.toolsTabView.setTabBarAutoHide(True)

        # add attribute for storing page notes
        self.notesDB = []
        self.notesDisplay = QListWidget(self)
        self.notesDisplay.setMaximumHeight(200)

        # add ToC to Tools View
        self.ToCListView = QListWidget()
        self.toolsTabView.addTab(self.ToCListView, "Table of Contents")

        # add notes list to tools view
        self.toolsTabView.addTab(self.notesDisplay, "Notes")

        # add tools view to the left-side splitter
        self.altViewSplit.addWidget(self.toolsTabView)




        # set right-side view -- SVG Viewer
        self.mainDisplay = QSvgWidget()

        
        self.vBoxMain = QVBoxLayout()
        self.vBoxMain.addWidget(self.mainDisplay)

        self.notesArea = QLineEdit()
        self.notesArea.setPlaceholderText("Add a note about this page...")
        self.notesArea.returnPressed.connect(self.onAddNote)
        
        self.vBoxMain.addWidget(self.notesArea)

        self.mainViewGroup = QGroupBox()
        self.mainViewGroup.setTitle("Main View")
        self.mainViewGroup.setLayout(self.vBoxMain)


        # join both sides together
        self.leftRightSplit = QSplitter(Qt.Horizontal)
        self.leftRightSplit.addWidget(self.altViewSplit)
        self.leftRightSplit.addWidget(self.mainViewGroup)        


        self.setCentralWidget(self.leftRightSplit)







    def initUIToolbar(self):

        mainMenu = self.menuBar() # get the menu bar already in use by this QMainWindow class
        
        fileMenu = mainMenu.addMenu("File")
        editMenu = mainMenu.addMenu("Edit")
        LayoutMenu = mainMenu.addMenu("Layout")
        AboutMenu = mainMenu.addMenu("About")

        saveAction = fileMenu.addAction("Save")
        quitAction = fileMenu.addAction("Exit Bettersheets")
        quitAction.triggered.connect(self.quitApp)
        copyAction = editMenu.addAction("Copy")
        resetAction = LayoutMenu.addAction("Reset Default Layout")

        # mainMenu.setNativeMenuBar(True)
        # mainMenu.show()

        self. toolBar = self.addToolBar("Tools")
        self.toolBar.addAction(saveAction)
        self.toolBar.addAction(copyAction)
        



    def contextMenuEvent(self, event):
        # return super().contextMenuEvent(event)
        contextMenu = QMenu()
        
        selectAction = contextMenu.addAction("Select Area")
        extractAction = contextMenu.addAction("Extract Content")
        openAction = contextMenu.addAction("Open PDF")
        closeAction = contextMenu.addAction("Close PDF")
        quitAction = contextMenu.addAction("Quit")

        triggered_action = contextMenu.exec_(self.mapToGlobal(event.pos()))

        if triggered_action == quitAction:
            self.quitApp()

    def quitApp(self):
        self.close()


    def onAddNote(self):
        print("note added")

        text = self.notesArea.text()

        if text:
            self.notesDB.append(text)
            self.notesDisplay.clear()
            self.notesDisplay.addItems(self.notesDB)
            self.notesArea.clear()

    def populatePDF(self, pdfPath, pageNumber):

        if pdfPath:
            self.document = PDFContext(pdfPath, pageNumber)
            
            ToC = self.document.getToC()
            ToC_headings_list = [x[1] for x in ToC]
            self.ToCListView.clear()
            self.ToCListView.addItems(ToC_headings_list)

            # display page in main view
            self.document.openPDF(pdfPath, pageNumber)
            self.SVGString = self.document.getRender(self.document.currentPageNumber)

            # set filename of current PDF
            self.pdfName = path.split(pdfPath)[1].split('.')[0]

            # write current page to .svg file
            file_loc = self._writeSVGToFile_(self.pdfName, pageNumber, self.SVGString)

            # open the file we just wrote to
            self.mainDisplay.load(file_loc)
            

    @staticmethod
    def _writeSVGToFile_(pdfName: str, pageNumber: int, svg_string: str) -> str:
        """
        return the full file path we just wrote
        """
        
        file_loc = f"./src/main/files/{pdfName}-page-{pageNumber}.svg"

        with open(file_loc , 'w') as f:  
            f.write(svg_string)

        print("File_loc: ", file_loc)
        return file_loc
Exemple #11
0
class PersonaUI(QWidget):
    """
    Widget for Persona creation view.

    :param MainFrame mainframe: application mainframe
    :param QWidget op: parent widget
    """
    def __init__(self, mainframe, op):
        QWidget.__init__(self)
        self.mainframe = mainframe
        self.op = op

        self.grid = QGridLayout()
        self.setLayout(self.grid)

        self.listP = None
        self.listLS = None
        self.listEL1 = None
        self.listEL2 = None

        self.nameT = None
        self.levelT = None
        self.textT = None
        self.strT = None
        self.magT = None
        self.endT = None
        self.agiT = None
        self.luckT = None

        self.createFrame = None
        self.buttonFrame = None
        self.bfgrid = None
        # Actual create frame variables.
        self.cfgrid = None
        self.lsdic = None
        self.slashO = None
        self.strikeO = None
        self.pierceO = None
        self.fireO = None
        self.iceO = None
        self.windO = None
        self.elecO = None
        self.darkO = None
        self.lightO = None
        self.arcO = None
        self.iSpellOs = None
        self.lsSpellO = None
        self.lslevel = None


        self.initUI(True)

    def initUI(self, infoDump):
        """
        Initializes the basic UI showing the list of Personas.
        Does a lot of stuff.

        :param dict infoDump: not sure lol
        """
        self.mainframe.setWindowTitle("Persona Creator")

        if not infoDump:
            self.createFrameDraw()

        self.initButtonFrame(infoDump)

        self.listP = QListWidget(self)
        self.grid.addWidget(self.listP, 0, 3, 2, 1)
        temp = json_reader.readPerNames()
        self.listP.addItems(temp)

    def initButtonFrame(self, infoDump):
        """
        Initializes the buttonframes that are present in all Persona creator views.

        :param dict infoDump: not sure lol
        """
        self.buttonFrame = QWidget(self)
        self.bfgrid = QGridLayout()
        self.buttonFrame.setLayout(self.bfgrid)

        self.grid.addWidget(self.buttonFrame, 3, 0, 1, 4)


        new = QPushButton(self.buttonFrame, text="New")
        new.clicked.connect(self.new)
        self.bfgrid.addWidget(new, 4, 0)

        back = QPushButton(self.buttonFrame, text="Back")
        back.clicked.connect(self.back)
        self.bfgrid.addWidget(back, 4, 4)

        remove = QPushButton(self.buttonFrame, text="Remove")
        remove.clicked.connect(self.remove)
        self.bfgrid.addWidget(remove, 4, 3)

        edit = QPushButton(self.buttonFrame, text="Edit")
        edit.clicked.connect(self.edit)
        self.bfgrid.addWidget(edit, 4, 2)

        if not infoDump:
            save = QPushButton(self.buttonFrame, text="Save")
            save.clicked.connect(self.save)
            self.bfgrid.addWidget(save, 4, 1)


    def createFrameDraw(self):
        """
        Initializes the GUI of the actual creation frame view.
        Does a LOT of stuff.
        """
        self.createFrame = QWidget(self)
        self.cfgrid = QGridLayout()
        self.createFrame.setLayout(self.cfgrid)
        self.grid.addWidget(self.createFrame, 0, 0, 2, 2)

        self.lsdic = {}

        nameL = QLabel(self.createFrame, text="Name:")
        self.cfgrid.addWidget(nameL, 0, 0)
        self.nameT = QLineEdit(self.createFrame)
        self.nameT.setFixedSize(100, 20)
        self.cfgrid.addWidget(self.nameT, 0, 1)

        strL = QLabel(self.createFrame, text="Str")
        self.cfgrid.addWidget(strL, 0, 2)
        self.strT = QLineEdit(self.createFrame)
        self.strT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.strT, 0, 3)
        magL = QLabel(self.createFrame, text="Mag")
        self.cfgrid.addWidget(magL, 1, 2)
        self.magT = QLineEdit(self.createFrame)
        self.magT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.magT, 1, 3)
        endL = QLabel(self.createFrame, text="End")
        self.cfgrid.addWidget(endL, 2, 2)
        self.endT = QLineEdit(self.createFrame)
        self.endT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.endT, 2, 3)
        agiL = QLabel(self.createFrame, text="Agi")
        self.cfgrid.addWidget(agiL, 3, 2)
        self.agiT = QLineEdit(self.createFrame)
        self.agiT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.agiT, 3, 3)
        luckL = QLabel(self.createFrame, text="Luck")
        self.cfgrid.addWidget(luckL, 4, 2)
        self.luckT = QLineEdit(self.createFrame)
        self.luckT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.luckT, 4, 3)

        resList = json_reader.data_list("resistances")
        resL = QLabel(self.createFrame, text="Resistance:")
        self.cfgrid.addWidget(resL, 0, 5)
        slashL = QLabel(self.createFrame, text="Slash")
        self.cfgrid.addWidget(slashL, 1, 5)
        self.slashO = QComboBox(self.createFrame)
        self.slashO.addItems(resList)
        self.slashO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.slashO, 1, 6)
        strikeL = QLabel(self.createFrame, text="Strike")
        self.cfgrid.addWidget(strikeL, 2, 5)
        self.strikeO = QComboBox(self.createFrame)
        self.strikeO.addItems(resList)
        self.strikeO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.strikeO, 2, 6)
        pierceL = QLabel(self.createFrame, text="Pierce")
        self.cfgrid.addWidget(pierceL, 3, 5)
        self.pierceO = QComboBox(self.createFrame)
        self.pierceO.addItems(resList)
        self.pierceO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.pierceO, 3, 6)
        fireL = QLabel(self.createFrame, text="Fire")
        self.cfgrid.addWidget(fireL, 4, 5)
        self.fireO = QComboBox(self.createFrame)
        self.fireO.addItems(resList)
        self.fireO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.fireO, 4, 6)
        iceL = QLabel(self.createFrame, text="Ice")
        self.cfgrid.addWidget(iceL, 5, 5)
        self.iceO = QComboBox(self.createFrame)
        self.iceO.addItems(resList)
        self.iceO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.iceO, 5, 6)
        elecL = QLabel(self.createFrame, text="Elec")
        self.cfgrid.addWidget(elecL, 6, 5)
        self.elecO = QComboBox(self.createFrame)
        self.elecO.addItems(resList)
        self.elecO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.elecO, 6, 6)
        windL = QLabel(self.createFrame, text="Wind")
        self.cfgrid.addWidget(windL, 7, 5)
        self.windO = QComboBox(self.createFrame)
        self.windO.addItems(resList)
        self.windO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.windO, 7, 6)
        lightL = QLabel(self.createFrame, text="Light")
        self.cfgrid.addWidget(lightL, 8, 5)
        self.lightO = QComboBox(self.createFrame)
        self.lightO.addItems(resList)
        self.lightO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.lightO, 8, 6)
        darkL = QLabel(self.createFrame, text="Dark")
        self.cfgrid.addWidget(darkL, 9, 5)
        self.darkO = QComboBox(self.createFrame)
        self.darkO.addItems(resList)
        self.darkO.setCurrentIndex(1)
        self.cfgrid.addWidget(self.darkO, 9, 6)

        spellList = json_reader.data_list("spells")
        self.listLS = QListWidget(self.createFrame)
        self.listLS.setFixedSize(200, 300)
        self.cfgrid.addWidget(self.listLS, 3, 7, 8, 2)

        newLS = QPushButton(self.createFrame, text="+")
        newLS.clicked.connect(self.addLS)
        self.cfgrid.addWidget(newLS, 2, 7)
        delLS = QPushButton(self.createFrame, text="DEL")
        delLS.clicked.connect(self.delLS)
        self.cfgrid.addWidget(delLS, 2, 8)

        lsl = QLabel(self.createFrame, text="Learned Spells:")
        self.cfgrid.addWidget(lsl, 0, 7, 1, 2)

        arcanaL = QLabel(self.createFrame, text="Arcana:")
        self.cfgrid.addWidget(arcanaL, 1, 0)
        arc_list = json_reader.data_list("arcanas")
        self.arcO = QComboBox(self.createFrame)
        self.arcO.addItems(arc_list)
        self.arcO.setCurrentIndex(0)
        self.cfgrid.addWidget(self.arcO, 1, 1)

        levelL = QLabel(self.createFrame, text="Level:")
        self.cfgrid.addWidget(levelL, 2, 0)
        self.levelT = QLineEdit(self.createFrame)
        self.levelT.setFixedSize(20, 20)
        self.cfgrid.addWidget(self.levelT, 2, 1)

        heritageL = QLabel(self.createFrame, text="Inherits:")
        self.cfgrid.addWidget(heritageL, 3, 0, 1, 2)

        elements = json_reader.data_list("elements")
        elements.append("Support")
        self.listEL1 = QComboBox(self.createFrame)
        self.listEL1.addItems(elements)
        self.cfgrid.addWidget(self.listEL1, 4, 0)
        self.listEL2 = QComboBox(self.createFrame)
        self.listEL2.addItems(elements)
        self.cfgrid.addWidget(self.listEL2, 4, 1)

        iSpellL = QLabel(self.createFrame, text="Initial Spells:")
        self.cfgrid.addWidget(iSpellL, 5, 0, 1, 2)
        self.iSpellOs = []
        for i in range(6, 9):
            temp = QComboBox(self.createFrame)
            temp.addItems(spellList)
            temp2 = QComboBox(self.createFrame)
            temp2.addItems(spellList)
            self.cfgrid.addWidget(temp, i, 0, 1, 2)
            self.cfgrid.addWidget(temp2, i, 2, 1, 2)
            self.iSpellOs.extend([temp, temp2])

        textL = QLabel(self.createFrame, text="Info:")
        self.cfgrid.addWidget(textL, 10, 0)
        self.textT = QTextEdit(self.createFrame)
        self.textT.setFixedSize(300, 100)
        self.cfgrid.addWidget(self.textT, 10, 1, 1, 5)

        self.lslevel = QLineEdit(self.createFrame)
        self.lslevel.setFixedSize(40, 20)
        self.lsSpellO = QComboBox(self.createFrame)
        self.lsSpellO.addItems(spellList)

        self.cfgrid.addWidget(self.lsSpellO, 1, 7)
        self.cfgrid.addWidget(self.lslevel, 1, 8)

    def addLS(self):
        """
        Add a learned spell to the list, based on what was entered.
        """
        print("Adding learned spell")
        chosenSpell = self.lsSpellO.currentText()
        if (int)(self.lslevel.text()) <= (int)(self.levelT.text()):
            popup("You cannot add a spell at an earlier level than the Persona's base level", "Critical")
            return
        if chosenSpell != "":
            print("Ok")
            self.lsdic[chosenSpell] = self.lslevel.text()
            self.listLS.addItem(chosenSpell + " at level " + self.lslevel.text())
            self.lslevel.setText("")
            self.lsSpellO.setCurrentIndex(0)
            return
        popup("You must choose a spell", "Critical")

    def delLS(self):
        """
        Remove the selected learned spell from the list
        """
        print("Deleting learned spell")
        key = ""
        i = 0
        while len(self.listLS.currentItem().text()) > i:
            if self.listLS.currentItem().text()[i] == " " and \
               self.listLS.currentItem().text()[i+1] == "a" and \
               self.listLS.currentItem().text()[i+2] == "t":  # TODO EWWWWWW
                break
            key += self.listLS.currentItem().text()[i]
            i = i + 1
        print(key)
        print(self.lsdic.pop(key))
        self.listLS.takeItem(self.listLS.currentRow())


    def loadPer(self, name):
        """
        Load a certain Persona from file.

        :param str name: name of Persona to load
        """
        data = json_reader.readOne(name, 'pers')
        self.nameT.setText(data["name"])
        self.textT.setText(data["desc"])
        self.strT.setText(data["stats"][0])
        self.magT.setText(data["stats"][1])
        self.endT.setText(data["stats"][2])
        self.agiT.setText(data["stats"][3])
        self.luckT.setText(data["stats"][4])
        self.levelT.setText(data["level"])

        self.arcO.setCurrentIndex(
            [self.arcO.itemText(i) for i in range(self.arcO.count())].index(data["arcana"])
        )

        self.listEL1.setCurrentIndex(
            [self.listEL1.itemText(i) for i in range(self.listEL1.count())].index(data["heritage"][0])
        )
        self.listEL2.setCurrentIndex(
            [self.listEL2.itemText(i) for i in range(self.listEL2.count())].index(data["heritage"][1])
        )

        self.slashO.setCurrentIndex(
            [self.slashO.itemText(i) for i in range(self.slashO.count())].index(data["resistance"][0])
        )
        self.strikeO.setCurrentIndex(
            [self.strikeO.itemText(i) for i in range(self.strikeO.count())].index(data["resistance"][1])
        )
        self.pierceO.setCurrentIndex(
            [self.pierceO.itemText(i) for i in range(self.pierceO.count())].index(data["resistance"][2])
        )
        self.fireO.setCurrentIndex(
            [self.fireO.itemText(i) for i in range(self.fireO.count())].index(data["resistance"][3])
        )
        self.iceO.setCurrentIndex(
            [self.iceO.itemText(i) for i in range(self.iceO.count())].index(data["resistance"][4])
        )
        self.elecO.setCurrentIndex(
            [self.elecO.itemText(i) for i in range(self.elecO.count())].index(data["resistance"][5])
        )
        self.windO.setCurrentIndex(
            [self.windO.itemText(i) for i in range(self.windO.count())].index(data["resistance"][6])
        )
        self.lightO.setCurrentIndex(
            [self.lightO.itemText(i) for i in range(self.lightO.count())].index(data["resistance"][7])
        )
        self.darkO.setCurrentIndex(
            [self.darkO.itemText(i) for i in range(self.darkO.count())].index(data["resistance"][8])
        )

        i = 0
        for combobox in self.iSpellOs:
            combobox.setCurrentIndex(
                [combobox.itemText(j) for j in range(combobox.count()-1)].index(data["spellDeck"][i])
            )
            i += 1

        self.lsdic = data["spellLearn"]
        self.listLS.clear()
        for spell, level in self.lsdic.items():
            self.listLS.addItem(spell + " at level " + level)

        print("Loaded " + data["name"])

    def edit(self):
        """
        Switch to edit view, also loads the selected Persona.
        """
        try:
            if self.listP.currentItem().text() != "":
                if self.createFrame and not popup("Override any unsaved changes?", "Warning"):
                    return
                self.loadPer(self.listP.currentItem().text())
        except AttributeError:  # To initialize createFrame UI before load
            if self.listP.currentItem().text() != "":
                temp = self.listP.currentItem().text()
                self.buttonFrame.close()
                self.initUI(False)
                self.loadPer(temp)
            else:
                return
        self.createFrame.show()
        self.mainframe.center()
        print("Changed to edit frame")

    def save(self):
        """
        Validate all info and save to file on disk.
        """
        if os.path.exists(json_reader.buildPath("data/pers/"+self.nameT.text()+".json")):
            if not popup("Override existing Persona "+self.nameT.text()+"?", "Question"):
                return
        print("Saving")
        spellDeck = []
        for combobox in self.iSpellOs:
            spellDeck.append(combobox.currentText())
        stats = [self.strT.text(), self.magT.text(), self.endT.text(), self.agiT.text(), self.luckT.text()]
        res = [self.slashO.currentText(), self.strikeO.currentText(), self.pierceO.currentText(),
               self.fireO.currentText(), self.iceO.currentText(), self.elecO.currentText(),
               self.windO.currentText(), self.lightO.currentText(), self.darkO.currentText()]
        try:
            (int)(self.levelT.text())
            (int)(self.strT.text())
            (int)(self.magT.text())
            (int)(self.endT.text())
            (int)(self.agiT.text())
            (int)(self.luckT.text())
        except ValueError:
            popup("There is a number entry that isn't valid.\nEntries requiring numbers are:\nLEVEL\nSTR"
                  "\nMAG\nEND\nAGI\nLUCK", "Critical")
            print("Not Saved")
            return
        if not (self.nameT.text() and not self.nameT.text().isspace()):
            popup("No name entered for your Persona. Name is a required field.", "Critical")
            print("No Name, not saved")
            return
        toWrite = Persona(
            self.nameT.text(),
            self.arcO.currentText(),
            self.levelT.text(),
            self.textT.toPlainText(),
            spellDeck,
            self.lsdic,
            stats,
            res,
            [self.listEL1.currentText(), self.listEL2.currentText()]
        )
        json_reader.writeOne(toWrite, 'pers')
        temp = self.nameT.text()
        if (temp not in [self.listP.item(i).text() for i in range(self.listP.count())]):
            self.listP.addItem(temp)
        self.loadPer(temp)
        print("Saved Persona")

    def remove(self):
        """
        Remove a created Persona from the list and delete the file on disk.
        """
        if self.listP.currentItem().text() == "":
            return
        if not popup(
                "Are you certain you want to completely remove this Persona?\n(Cannot be undone)", "Warning"
            ):
            return
        print("Removing Persona " + self.listP.currentItem().text())
        json_reader.deletePer(self.listP.currentItem().text())
        self.listP.takeItem(
            [self.listP.item(i).text() for i in range(self.listP.count())].index(
                self.listP.currentItem().text())
        )

    def new(self):
        """
        Open an empty Persona edit view.
        """
        if self.createFrame and not popup("Override any unsaved changes?", "Warning"):
            return
        if self.createFrame:
            self.createFrame.close()
        self.buttonFrame.close()
        self.initUI(False)
        self.createFrame.show()
        self.mainframe.center()
        print("Created")

    def back(self):
        """
        Return to the parent widget.
        """
        print("Returned to main screen")
        self.mainframe.changeState(self.op)
class Randomizer(QWidget):
    """A game randomizer for selecting a random game to play
       from the user's collection. User can select which
       platforms to choose from.
       gamesData: Raw table data in list of dictionaries"""
    def __init__(self, gamesData: list, platformsData: list, genresData: list):
        super(Randomizer, self).__init__()

        self._consoleItems = platformsData
        self._genreItems = genresData
        self._gamesData = gamesData
        self._games = []  # For holding the games to randomize
        self._gameCount = 0
        self._coverdir = path.join("data", "images", "covers")

        self.consoleLabel = QLabel("Platforms")
        self.consoleList = QListWidget()
        self.consoleList.addItems(self._consoleItems)
        self.consoleList.setSelectionMode(QAbstractItemView.MultiSelection)
        self.consoleList.setMaximumWidth(350)
        self.consoleList.itemClicked.connect(self._updateGameCount)

        self.genreLabel = QLabel("Genres")
        self.genreMatchExclusiveCB = QCheckBox("Match exclusive")
        self.genreMatchExclusiveCB.setToolTip(
            "Only match games which exclusively contain the selected genres.")
        self.genreMatchExclusiveCB.setChecked(False)
        self.genreMatchExclusiveCB.stateChanged.connect(self._updateGameCount)
        self.genreList = QListWidget()
        self.genreList.addItems(self._genreItems)
        self.genreList.setSelectionMode(QAbstractItemView.MultiSelection)
        self.genreList.setMaximumWidth(350)
        self.genreList.itemClicked.connect(self._updateGameCount)

        self.btnAll = QPushButton("Select All")
        self.btnAll.setMaximumSize(self.btnAll.sizeHint())
        self.btnAll.clicked.connect(self.consoleList.selectAll)
        self.btnAll.clicked.connect(self.genreList.selectAll)
        self.btnAll.clicked.connect(self._updateGameCount)
        self.btnNone = QPushButton("Select None")
        self.btnNone.setMaximumSize(self.btnNone.sizeHint())
        self.btnNone.clicked.connect(self.consoleList.clearSelection)
        self.btnNone.clicked.connect(self.genreList.clearSelection)
        self.btnNone.clicked.connect(self._updateGameCount)
        self._btnRnd = QPushButton("Randomize")
        self._btnRnd.setMaximumSize(self._btnRnd.sizeHint())
        self._btnRnd.clicked.connect(self._randomize)

        self._lblFont = QFont()
        self._lblFont.setPointSize(14)
        self._lblFont.setBold(True)
        self._lblPlay = QLabel()
        self._lblPlay.setAlignment(Qt.AlignCenter)
        self._lblPlay.setFont(self._lblFont)
        self._lblTitle = QLabel()
        self._lblTitle.setAlignment(Qt.AlignCenter)
        self._lblTitle.setFont(self._lblFont)
        self._lblTitle.setWordWrap(True)

        # Cover image
        self._cover = QLabel()
        self._cover.setVisible(False)
        self._cover.setAlignment(Qt.AlignCenter)
        p = QPixmap(path.join(self._coverdir, "none.png"))
        w = self._cover.width()
        h = self._cover.height()
        self._cover.setPixmap(
            p.scaled(w, h, Qt.KeepAspectRatio, Qt.SmoothTransformation))

        self._hboxButtons = QHBoxLayout()
        self._vboxLists = QVBoxLayout()
        self._vboxConsoles = QVBoxLayout()
        self._hboxGenres = QHBoxLayout()
        self._vboxGenres = QVBoxLayout()
        self._vboxResult = QVBoxLayout()
        self._grid = QGridLayout()
        self._hboxButtons.addWidget(self.btnAll, 0)
        self._hboxButtons.addWidget(self.btnNone, 0)
        self._hboxButtons.addWidget(self._btnRnd, 0)
        self._vboxConsoles.addWidget(self.consoleLabel, 0)
        self._vboxConsoles.addWidget(self.consoleList, 1)
        self._hboxGenres.addWidget(self.genreLabel, 0)
        self._hboxGenres.addWidget(self.genreMatchExclusiveCB, 0)
        self._vboxGenres.addWidget(self.genreList, 1)
        self._vboxLists.addSpacing(10)
        self._vboxLists.addLayout(self._vboxConsoles, 1)
        self._vboxLists.addSpacing(10)
        self._vboxLists.addLayout(self._hboxGenres, 0)
        self._vboxLists.addLayout(self._vboxGenres, 1)
        self._vboxResult.addStretch(3)
        self._vboxResult.addWidget(self._lblPlay, 0)
        self._vboxResult.addWidget(self._lblTitle, 0)
        self._vboxResult.addSpacing(50)
        self._vboxResult.addWidget(self._cover, 0)
        self._vboxResult.addStretch(3)
        self._grid.setMargin(0)
        self._grid.setSpacing(0)
        self._grid.addLayout(self._vboxLists, 0, 0)
        self._grid.addLayout(self._hboxButtons, 1, 0)
        self._grid.addLayout(self._vboxResult, 0, 1, 1, -1)

        self.widget = QWidget()
        self.widget.setLayout(self._grid)

    def _getSelectedItems(self) -> tuple:
        return [x.text() for x in self.consoleList.selectedItems()
                ], [x.text() for x in self.genreList.selectedItems()]

    def _randomize(self):
        platforms, genres = self._getSelectedItems()

        if len(self._games) > 0 and (len(platforms) > 0 or len(genres) > 0):
            choice = randint(0, len(self._games) - 1)
            self._lblPlay.setText("You will play:")
            self._lblTitle.setText(
                f"{self._games[choice]['name']}" if len(platforms) == 1 else
                f"{self._games[choice]['name']} [{self._games[choice]['platform']}]"
            )
            # Cover image
            cover = str(self._games[choice]['id']) + ".jpg"
            if path.exists(path.join(self._coverdir, cover)):
                # Update cover image if the game has one
                pixmap = path.join(self._coverdir, cover)
                self._cover.setVisible(True)
            else:
                pixmap = path.join(self._coverdir, "none.png")
                self._cover.setVisible(False)
            p = QPixmap(pixmap)
            w = self._cover.width()
            h = self._cover.height()
            self._cover.setPixmap(
                p.scaled(w, h, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        elif len(self._games) == 0 and (len(platforms) > 0 or len(genres) > 0):
            self._lblPlay.setText("")
            self._lblTitle.setText("No games found with those criteria.")
            self._cover.setVisible(False)
        else:
            self._lblPlay.setText("")
            self._lblTitle.setText("Select at least one console or genre...")
            self._cover.setVisible(False)

    def _updateGameCount(self):
        platforms, genres = self._getSelectedItems()
        self._gameCount = 0
        self._games = []

        if len(platforms) > 0 or len(genres) > 0:
            for row in self._gamesData:
                if len(platforms) > 0 and len(genres) > 0:
                    if row["platform"] in platforms:
                        if self.genreMatchExclusiveCB.isChecked():
                            count = 0
                            for genre in row["genre"].split(", "):
                                if genre in genres:
                                    count += 1
                                else:  # Not exclusive
                                    count = 0
                                    break
                            if count == len(genres):
                                self._gameCount += 1
                                self._games.append(row)
                        else:
                            for genre in row["genre"].split(", "):
                                if genre in genres:
                                    self._gameCount += 1
                                    self._games.append(row)
                                    break  # We only need to match with one genre
                elif len(platforms) > 0 and len(genres) == 0:
                    if row["platform"] in platforms:
                        self._gameCount += 1
                        self._games.append(row)
                elif len(platforms) == 0 and len(genres) > 0:
                    if self.genreMatchExclusiveCB.isChecked():
                        count = 0
                        for genre in row["genre"].split(", "):
                            if genre in genres:
                                count += 1
                            else:  # Not exclusive
                                count = 0
                                break
                        if count == len(genres):
                            self._gameCount += 1
                            self._games.append(row)
                    else:
                        for genre in row["genre"].split(", "):
                            if genre in genres:
                                self._gameCount += 1
                                self._games.append(row)
                                break  # We only need to match with one genre

    def gameCount(self) -> int:
        return self._gameCount

    def updateLists(self, gamesData: list, platformsData: list,
                    genresData: list):
        self._gamesData = gamesData
        self._consoleItems = platformsData
        self._genreItems = genresData
        self.consoleList.clear()
        self.genreList.clear()

        self.consoleList.addItems(self._consoleItems)
        self.genreList.addItems(self._genreItems)
Exemple #13
0
class DatasheetView(QMainWindow):
    def __init__(self, pdfPath=None, openPages=[1]):

        super().__init__()

        if pdfPath:
            self.myPdfContext = PDFContext(pdfPath, openPages)

        # store diretory for debugging purposes
        self.svgDirectory = self.myPdfContext.directory

        # window dimensions
        self.top = 300
        self.left = 800
        self.width = 860
        self.height = 980

        self.setGeometry(self.left, self.top, self.width, self.height)

        # window title
        self.setWindowTitle("BetterSheets")

        # sets up main layout -- splitters
        self.initUILayout()
        self.initUIToolbar()
        self.initToC()

        # self.initPdfViewer()  # must be called after initUI to ensure PDFContext object exists
        self.show()

        print(self.mainDisplay.getVisibleChild())

    def initUILayout(self):

        # set left-side, Dynamic View
        self.dynamicViewDisplay = QLabel()

        self.vBoxA = QVBoxLayout()
        self.vBoxA.addWidget(self.dynamicViewDisplay)

        self.groupA = QGroupBox()
        self.groupA.setTitle("Dynamic Veiw")
        self.groupA.setLayout(self.vBoxA)

        # set left-side, Static View
        self.staticViewDisplay = QLabel()

        self.vBoxB = QVBoxLayout()
        self.vBoxB.addWidget(self.staticViewDisplay)

        self.groupB = QGroupBox()
        self.groupB.setTitle("Static View")
        self.groupB.setLayout(self.vBoxB)

        # add Dynamic and Static Views to resizeable left-side Splitter
        self.altViewSplit = QSplitter(Qt.Vertical)
        self.altViewSplit.addWidget(self.groupA)
        self.altViewSplit.addWidget(self.groupB)

        # set up Tools View section
        self.toolsTabView = QTabWidget()
        self.toolsTabView.setTabsClosable(True)
        self.toolsTabView.setMovable(True)
        self.toolsTabView.setDocumentMode(True)
        # self.toolsTabView.setTabBarAutoHide(True)

        # add attribute for storing page notes
        self.notesDB = []
        self.notesDisplay = QListWidget(self)
        self.notesDisplay.setMaximumHeight(200)

        # add ToC to Tools View
        self.ToCListView = QListWidget()
        self.toolsTabView.addTab(self.ToCListView, "Table of Contents")

        # add notes list to tools view
        self.toolsTabView.addTab(self.notesDisplay, "Notes")

        # add tools view to the left-side splitter
        self.altViewSplit.addWidget(self.toolsTabView)

        # set up main viewport
        self.mainDisplay = QDatasheetPageDisplayWidget(self.myPdfContext)
        self.mainDisplay.renderPages(1, 4)

        self.mainScroller = QScrollArea(self)
        self.mainScroller.setWidget(self.mainDisplay)
        self.mainScroller.setWidgetResizable(True)
        self.mainScroller.setBackgroundRole(QtGui.QPalette.Dark)
        self.mainScroller.setFixedHeight(800)

        print(self.mainScroller.viewport().childrenRect())

        # set up document tools

        self.hBoxDocTools = QHBoxLayout()

        self.searchLabel = QLabel("Search: ")
        self.searchBox = QLineEdit()
        self.searchBox.setPlaceholderText("Search")

        # self.

        self.vBoxMain = QVBoxLayout()
        self.vBoxMain.addWidget(self.mainScroller)

        self.notesArea = QLineEdit()
        self.notesArea.setPlaceholderText("Add a note about this page...")
        self.notesArea.returnPressed.connect(self.onAddNote)

        self.vBoxMain.addWidget(self.notesArea)

        self.mainViewGroup = QGroupBox()
        self.mainViewGroup.setTitle("Main View")
        self.mainViewGroup.setLayout(self.vBoxMain)

        # join both sides together
        self.leftRightSplit = QSplitter(Qt.Horizontal)
        self.leftRightSplit.addWidget(self.altViewSplit)
        self.leftRightSplit.addWidget(self.mainViewGroup)

        self.setCentralWidget(self.leftRightSplit)

    def initUIToolbar(self):

        mainMenu = self.menuBar(
        )  # get the menu bar already in use by this QMainWindow subclass

        fileMenu = mainMenu.addMenu("File")
        editMenu = mainMenu.addMenu("Edit")
        LayoutMenu = mainMenu.addMenu("Layout")
        AboutMenu = mainMenu.addMenu("About")

        saveAction = fileMenu.addAction("Save")
        quitAction = fileMenu.addAction("Exit Bettersheets")
        quitAction.triggered.connect(self.quitApp)
        copyAction = editMenu.addAction("Copy")
        resetAction = LayoutMenu.addAction("Reset Default Layout")

        self.toolBar = self.addToolBar("Tools")
        self.toolBar.addAction(saveAction)
        self.toolBar.addAction(copyAction)

    def contextMenuEvent(self, event):
        # return super().contextMenuEvent(event)
        contextMenu = QMenu()

        selectAction = contextMenu.addAction("Select Area")
        extractAction = contextMenu.addAction("Extract Content")
        openAction = contextMenu.addAction("Open PDF")
        closeAction = contextMenu.addAction("Close PDF")
        quitAction = contextMenu.addAction("Quit")

        triggered_action = contextMenu.exec_(self.mapToGlobal(event.pos()))

        if triggered_action == quitAction:
            self.quitApp()

    def quitApp(self):
        self.close()

    def onAddNote(self):
        print("note added")

        text = self.notesArea.text()

        if text:
            self.notesDB.append(text)
            self.notesDisplay.clear()
            self.notesDisplay.addItems(self.notesDB)
            self.notesArea.clear()

    def initPdfViewer(self, openPages: int):
        pass

    def initToC(self):

        # get table of contents
        ToC = self.myPdfContext.getToC()
        ToC_headings_list = [x[1] for x in ToC]

        self.ToCListView.clear()
        self.ToCListView.addItems(ToC_headings_list)
Exemple #14
0
class MainWindowUi(QMainWindow):
    """sets up ui properties of MainWindowUi class"""
    def __init__(self) -> None:
        """inits MainWindow class

        configuring parameters of MainWindow class and inherits from QtWidget.QMainWindow
        loads .ui file sets up file and directory path vars, inits click events(menuebar, coboboxes, btns) and
        shows gui the first time

        Returns:
            None"""
        super(MainWindowUi, self).__init__()
        self.setWindowTitle("It_Hilfe")
        self.resize(820, 450)
        self.setWindowIcon(QIcon("./data/favicon2.png"))
        self.setMinimumSize(700, 250)

        self.file_path = None
        self.dir = None
        self.last_open_file_path = None
        self.last_open_file_dir = None
        self.initial_theme = None
        self.registered_devices = {}
        self.user_config_file = "./data/user_config.json"

        # setup stackedwidget
        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)
        self.setup_menubar()
        self.setup_p_view()
        self.setup_p_register()
        self.setup_p_create()
        self.setup_p_preferences()
        self.setup_signals()

        self.font = QFont()
        self.font.setPointSize(9)

        self.validate(self.set_user_preferences,
                      file_path=self.user_config_file,
                      schema=validate_json.ItHilfeUserPreferencesSchema,
                      forbidden=[""])

        # setup statusbar
        self.statusbar = self.statusBar()

        self.stacked_widget.setCurrentWidget(self.p_view)

    def setup_menubar(self) -> None:
        """inits menubar

        Returns:
            None"""
        self.menu_Bar = self.menuBar()

        menu_file = self.menu_Bar.addMenu("file")
        self.action_open = QAction("open")
        self.action_save = QAction("save")
        self.action_new = QAction("new")
        self.action_print = QAction("print")
        self.action_preferences = QAction("preferences")
        self.action_hide_menu_bar = QAction("hide menubar")
        self.action_print.setShortcut(QKeySequence("Ctrl+p"))
        self.action_open.setShortcut(QKeySequence("Ctrl+o"))
        self.action_save.setShortcut(QKeySequence("Ctrl+s"))
        self.action_hide_menu_bar.setShortcut(QKeySequence("Ctrl+h"))
        self.action_hide_menu_bar.setIcon(QIcon("./data/show_hide.ico"))
        self.action_print.setIcon(QIcon("./data/print2.ico"))
        self.action_open.setIcon(QIcon("./data/open.ico"))
        self.action_save.setIcon(QIcon("./data/save.ico"))
        self.action_new.setIcon(QIcon("./data/newfile.ico"))
        self.action_preferences.setIcon(QIcon("./data/preferences.ico"))

        menu_file.addAction(self.action_open)
        menu_file.addAction(self.action_save)
        menu_file.addAction(self.action_new)
        menu_file.addAction(self.action_print)
        menu_file.addAction(self.action_preferences)

        menu_edit = self.menu_Bar.addMenu("edit")
        self.action_register = QAction("register")
        self.action_register.setShortcut(QKeySequence("Ctrl+n"))
        self.action_register.setIcon(QIcon("./data/register.ico"))

        menu_edit.addAction(self.action_register)

        menu_view = self.menu_Bar.addMenu("view")
        menu_view.addAction(self.action_hide_menu_bar)

    def setup_p_view(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""
        self.p_view = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_view)

        self.model = QStandardItemModel(self.p_view)
        self.model.setHorizontalHeaderLabels(labels)

        self.filters = []
        source_model = self.model
        for filter_num in range(7):
            filter = QSortFilterProxyModel()
            filter.setSourceModel(source_model)
            filter.setFilterKeyColumn(filter_num)
            source_model = filter
            self.filters.append(filter)

        delegate = ComboDelegate()
        self.table = QtWidgets.QTableView(self.p_view)
        self.table.setModel(self.filters[-1])
        self.table.setItemDelegateForColumn(2, delegate)
        self.table.horizontalHeader().setStretchLastSection(True)
        self.table.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.header = FilterHeader(self.table)
        self.header.set_filter_boxes()
        self.header.setMaximumHeight(50)
        self.table.setHorizontalHeader(self.header)

        self.bt_burger = QPushButton(self.p_view)
        self.bt_burger.setIcon(QIcon("./data/menu2.svg"))
        self.bt_burger.setIconSize(QSize(30, 30))
        self.bt_burger.setToolTip('slide out description')
        l_burger = QLabel("menu", self.p_view)

        self.bt_register_new = QPushButton(self.p_view)
        self.bt_register_new.setIcon(QIcon("./data/add.ico"))
        self.bt_register_new.setIconSize(QSize(30, 30))
        self.bt_register_new.setToolTip("register new")
        l_register_new = QLabel("register new", self.p_view)

        self.bt_delete_column = QPushButton(self.p_view)
        self.bt_delete_column.setIcon(QIcon("./data/remove.ico"))
        self.bt_delete_column.setIconSize(QSize(30, 30))
        self.bt_delete_column.setToolTip(
            "delete columns with min 1 cell selected")
        l_delete = QLabel("delete column", self.p_view)

        self.bt_hide_show_filter = QPushButton(self.p_view)
        self.bt_hide_show_filter.setIcon(QIcon("./data/show_hide.ico"))
        self.bt_hide_show_filter.setIconSize(QSize(30, 30))
        self.bt_hide_show_filter.setToolTip("hide/show filter input")
        l_hide_show = QLabel("hide/show", self.p_view)

        self.left_btn_frame = QFrame(self.p_view)
        self.left_btn_frame.setMaximumWidth(40)
        self.left_btn_frame.setContentsMargins(0, 0, 0, 0)

        self.left_menu_frame = QFrame(self.p_view)
        self.left_menu_frame.setMaximumWidth(0)
        self.left_menu_frame.setContentsMargins(0, 0, 0, 0)

        p_view_layout2 = QtWidgets.QVBoxLayout(self.left_btn_frame)
        p_view_layout2.addWidget(self.bt_burger)
        p_view_layout2.addWidget(self.bt_register_new)
        p_view_layout2.addWidget(self.bt_delete_column)
        p_view_layout2.addWidget(self.bt_hide_show_filter)
        p_view_layout2.setAlignment(Qt.AlignTop)
        p_view_layout2.setContentsMargins(0, 0, 0, 0)

        self.p_view_layout3 = QtWidgets.QVBoxLayout(self.left_menu_frame)
        self.p_view_layout3.addWidget(l_burger)
        self.p_view_layout3.addWidget(l_register_new)
        self.p_view_layout3.addWidget(l_delete)
        self.p_view_layout3.addWidget(l_hide_show)
        self.p_view_layout3.setAlignment(Qt.AlignTop | Qt.AlignCenter)
        self.p_view_layout3.setContentsMargins(0, 0, 0, 0)
        self.p_view_layout3.setSpacing(25)

        p_view_layout = QHBoxLayout(self.p_view)
        p_view_layout.setContentsMargins(0, 0, 0, 0)
        p_view_layout.addWidget(self.left_btn_frame)
        p_view_layout.addWidget(self.left_menu_frame)
        p_view_layout.addWidget(self.table)
        self.p_view.setLayout(p_view_layout)

        self.p_view.addAction(self.action_open)
        self.p_view.addAction(self.action_save)
        self.p_view.addAction(self.action_new)
        self.p_view.addAction(self.action_print)
        self.p_view.addAction(self.action_register)
        self.p_view.addAction(self.action_hide_menu_bar)

    def setup_p_register(self) -> None:
        """inits stacked widget page widgets

        Returns:
            None"""

        self.p_register = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_register)

        l_user = QtWidgets.QLabel("Username", self.p_register)
        self.in_username = QtWidgets.QLineEdit(self.p_register)
        l_devicename = QtWidgets.QLabel("Devicename", self.p_register)
        self.in_devicename = QtWidgets.QLineEdit(self.p_register)
        l_devicetype = QtWidgets.QLabel("DeviceType", self.p_register)
        self.in_combobox_devicetype = QtWidgets.QComboBox(self.p_register)
        l_os = QtWidgets.QLabel("OS", self.p_register)
        self.in_combobox_os = QtWidgets.QComboBox(self.p_register)
        l_comment = QtWidgets.QLabel("Comment", self.p_register)
        self.in_comment = QtWidgets.QTextEdit(self.p_register)
        self.bt_enter_register = QPushButton("register", self.p_register)
        self.bt_cancel_register = QPushButton("cancel", self.p_register)

        p_register_layout = QtWidgets.QVBoxLayout(self.p_register)
        p_register_layout.addWidget(l_user)
        p_register_layout.addWidget(self.in_username)
        p_register_layout.addWidget(l_devicename)
        p_register_layout.addWidget(self.in_devicename)
        p_register_layout.addWidget(l_devicetype)
        p_register_layout.addWidget(self.in_combobox_devicetype)
        p_register_layout.addWidget(l_os)
        p_register_layout.addWidget(self.in_combobox_os)
        p_register_layout.addWidget(l_comment)
        p_register_layout.addWidget(self.in_comment)
        p_register_layout.addWidget(self.bt_enter_register)
        p_register_layout.addWidget(self.bt_cancel_register)

    def setup_p_create(self) -> None:
        """inits stacked widget page widget

        Returns:
            None"""

        self.p_create = QtWidgets.QWidget()
        self.stacked_widget.addWidget(self.p_create)

        l_new_filepath = QtWidgets.QLabel("new filepath", self.p_create)
        self.bt_mod_new_path = QPushButton("mod filepath", self.p_create)
        self.in_new_filepath = QtWidgets.QLineEdit(self.p_create)
        l_new_filename = QtWidgets.QLabel("new filename", self.p_create)
        self.in_new_filename = QtWidgets.QLineEdit(self.p_create)
        self.bt_create = QPushButton("create", self.p_create)
        self.bt_cancel_create = QPushButton("cancel", self.p_create)

        p_create_layout = QtWidgets.QVBoxLayout(self.p_create)
        p_create_layout.addWidget(l_new_filepath)
        p_create_layout.addWidget(self.in_new_filepath)
        p_create_layout.addWidget(l_new_filename)
        p_create_layout.addWidget(self.in_new_filename)
        p_create_layout.addStretch(100)
        p_create_layout.addWidget(self.bt_mod_new_path)
        p_create_layout.addWidget(self.bt_create)
        p_create_layout.addWidget(self.bt_cancel_create)

    def setup_p_preferences(self) -> None:
        """inits setup_p_preferences stacked widget page widget

            Returns:
                None"""

        self.p_preferences = QWidget()
        self.p_preferences.resize(500, 250)
        self.p_preferences.setWindowTitle("preferences")
        self.list_Widget = QListWidget(self.p_preferences)
        self.list_Widget.addItems(["appearance", "about"])
        self.list_Widget.setMaximumWidth(100)

        self.stacked_widget_preferences = QStackedWidget(self.p_preferences)

        # setup appearance
        self.apperence_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.apperence_widget)
        self.in_combo_themes = QComboBox(self.apperence_widget)
        self.in_combo_themes.addItems(["dark_theme", "light_theme"])

        self.in_combo_theme_initial = QComboBox(self.apperence_widget)
        self.in_combo_theme_initial.addItems(["dark_theme", "light_theme"])

        self.text_size_slider = QSlider(QtCore.Qt.Orientation.Horizontal,
                                        self.apperence_widget)
        self.text_size_slider.setTickPosition(QSlider.TickPosition.TicksAbove)
        self.text_size_slider.setMaximum(15)
        self.text_size_slider.setMinimum(8)

        stacked_widget_preferences_layout = QGridLayout(self.apperence_widget)
        stacked_widget_preferences_layout.setAlignment(QtCore.Qt.AlignTop)
        stacked_widget_preferences_layout.addWidget(QLabel("theme"), 0, 0)
        stacked_widget_preferences_layout.addWidget(self.in_combo_themes, 0, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("initial theme"), 1,
                                                    0)
        stacked_widget_preferences_layout.addWidget(
            self.in_combo_theme_initial, 1, 1)
        stacked_widget_preferences_layout.addWidget(QLabel("Fontsize"), 2, 0)
        stacked_widget_preferences_layout.addWidget(self.text_size_slider, 2,
                                                    1)

        self.about_widget = QWidget()
        self.stacked_widget_preferences.addWidget(self.about_widget)

        about_text_edit = QTextEdit(self.about_widget)
        about_text_edit.setText(
            "developed by Maurice Jarck\nwith kind support from Shuai Lou\n07.2020-04.2021"
        )
        about_text_edit.setEnabled(False)
        stacked_widget_about_layout = QGridLayout(self.about_widget)
        stacked_widget_about_layout.addWidget(about_text_edit)

        p_apperance_layout = QHBoxLayout(self.p_preferences)
        p_apperance_layout.addWidget(self.list_Widget)
        p_apperance_layout.addWidget(self.stacked_widget_preferences)

    def setup_signals(self) -> None:
        """connects signals

        Returns:
            None"""

        # header
        for filter, editor in zip(self.filters, self.header.editors):
            editor.textChanged.connect(filter.setFilterRegExp)

        # line edit
        self.in_new_filename.returnPressed.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))

        # comboboxes
        self.in_combobox_devicetype.addItems(
            ["choose here"] + [x.__name__ for x in valid_devices])
        self.in_combobox_devicetype.currentIndexChanged.connect(
            lambda: self.update_combobox(
                self.in_combobox_os, valid_devices[
                    self.in_combobox_devicetype.currentIndex() - 1].expected_OS
            ))
        self.in_combo_themes.currentIndexChanged.connect(
            lambda: self.change_theme(self.in_combo_themes.currentText()))
        self.in_combo_theme_initial.currentTextChanged.connect(lambda: setattr(
            self, "initial_theme", self.in_combo_theme_initial.currentText()))
        # btns
        self.bt_delete_column.clicked.connect(self.delete)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(37, 47, "height", self.header, b"maximumHeight"))
        self.bt_hide_show_filter.clicked.connect(self.header.hide_show)
        # self.bt_hide_show_filter.clicked.connect(lambda: self.toggle_hide_show_ani(30, 44, "height", self.header, b"maximumHeight"))
        self.bt_register_new.clicked.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.bt_enter_register.clicked.connect(lambda: self.validate(
            self.register,
            line_edit_list=[self.in_username, self.in_devicename],
            combo_box_list=[self.in_combobox_devicetype, self.in_combobox_os],
            forbidden=list(self.registered_devices.keys()),
            checkfname=True))
        self.bt_create.clicked.connect(lambda: self.validate(
            self.new,
            line_edit_list=[self.in_new_filepath, self.in_new_filename],
            data=False))
        self.bt_mod_new_path.clicked.connect(lambda: self.new(True))
        self.bt_burger.clicked.connect(lambda: self.toggle_hide_show_ani(
            0,
            66,
            "width",
            self.left_menu_frame,
            b"maximumWidth",
        ))
        # menu bar
        self.action_register.triggered.connect(
            lambda: self.stacked_widget.setCurrentWidget(self.p_register))
        self.action_open.triggered.connect(self.get_open_file_path)
        self.action_save.triggered.connect(self.save)
        self.action_new.triggered.connect(lambda: self.new(True))
        self.action_print.triggered.connect(
            lambda: self.validate(self.print, data=False, checkfname=True))
        self.action_hide_menu_bar.triggered.connect(
            lambda: self.toggle_hide_show(self.menu_Bar))
        self.action_preferences.triggered.connect(self.p_preferences.show)
        # cancel
        self.bt_cancel_register.clicked.connect(lambda: self.cancel([
            self.in_username, self.in_devicename, self.in_combobox_os, self.
            in_comment
        ]))

        # list widget
        self.list_Widget.currentRowChanged.connect(
            lambda: self.stacked_widget_preferences.setCurrentIndex(
                self.list_Widget.currentIndex().row()))

        # slider
        self.text_size_slider.sliderMoved.connect(
            lambda: self.change_font_size(self.text_size_slider.value()))
        # self.text_size_slider.sliderMoved.connect(lambda: print(self.text_size_slider.value()))

    def change_theme(self, theme) -> None:
        """changes theme according to combobox selection

        Returns:
            None"""

        with open(f"./data/{theme}.css", "r") as file:
            stylesheed = " ".join(file.readlines())
            self.setStyleSheet(stylesheed)
            self.p_preferences.setStyleSheet(stylesheed)

        if self.in_combo_themes.currentText() == "dark_theme":

            self.left_btn_frame.setStyleSheet(
                u"background: #455364; border: 0px solid;")
            self.p_view_layout3.setSpacing(30)

        else:
            self.left_btn_frame.setStyleSheet(
                u"background: #ADADAD; border: 0px solid;")
            self.p_view_layout3.setSpacing(25)

        return self.in_combo_themes.currentText()

    def toggle_hide_show_ani(self, collapsed_val: int, expanded_val: int,
                             actual: str, to_animate, property: bytes):
        """interpolates over a defined range of vales and sets it to a given property of a given widget"""
        if getattr(to_animate, actual)() == expanded_val:
            destination = collapsed_val
        else:
            destination = expanded_val
        print(getattr(to_animate, actual)(), destination)
        self.ani = QPropertyAnimation(to_animate, property)
        self.ani.setDuration(300)
        self.ani.setStartValue(getattr(to_animate, actual)())
        self.ani.setEndValue(destination)
        self.ani.setEasingCurve(QEasingCurve.Linear)
        self.ani.start()

    def toggle_hide_show(self, widget: QWidget) -> None:
        """toggles visibiliy of a given widget
        Arg:
            widget: widget which is aimed to be hidden or shown
        Returs:
            None"""

        if widget.isVisible():
            widget.hide()
        else:
            widget.show()

    def reopen_last_file(self) -> None:
        """asks for reopening of the last opened file"""
        if self.last_open_file_path != "" or self.last_open_file_path is not None:
            reopen_dialog = QMessageBox.question(
                self.p_view, "reopen last file?",
                "Do you want to reopen the last edited file?",
                QMessageBox.Yes | QMessageBox.No)
            if reopen_dialog == QMessageBox.Yes:
                self.file_path = self.last_open_file_path
                self.load()

    def change_font_size(self, size: int) -> None:
        """changes all font sizes"""
        self.font.setPointSize(size)
        self.menu_Bar.setFont(self.font)
        self.header.setFont(self.font)
        self.table.setFont(self.font)
        self.p_preferences.setFont(self.font)

    def set_user_preferences(self) -> None:
        """Reads user_config file and sets its propertys"""
        with open(self.user_config_file, "r") as config_file:
            data = dict(json.load(config_file))

            self.last_open_file_path = data["last_open_file_path"]
            self.initial_theme = data['initial_theme']
            self.change_font_size(data['font_size'])
            self.text_size_slider.setValue(data['font_size'])
            self.in_combo_theme_initial.setCurrentText(self.initial_theme)
            self.in_combo_themes.setCurrentText(self.initial_theme)

            with open(f"./data/{self.initial_theme}.css") as file:
                style_sheed = " ".join(file.readlines())
                self.setStyleSheet(style_sheed)
                self.p_preferences.setStyleSheet(style_sheed)

            self.bt_burger.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_register_new.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_delete_column.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.bt_hide_show_filter.setStyleSheet(
                "border: 0px solid; background: transparent;")
            self.left_menu_frame.setStyleSheet(u" border: 0px solid;")

            if self.initial_theme == "dark_theme":
                self.left_btn_frame.setStyleSheet(
                    u"background: #455364; border: 0px solid;")

            else:
                self.left_btn_frame.setStyleSheet(
                    u"background: #ADADAD; border: 0px solid;")

    def cancel(self, widgets: list) -> None:
        """click event for all cancel buttons

        shows fist page in stacked widget and clears all widgets in widgets

        Args:
               widgets: defines list containing widgets to clear, only widgets with method .clear() are possible

        Returns:
            None"""
        for widget in widgets:
            widget.clear()
        self.stacked_widget.setCurrentWidget(self.p_view)

    def update_combobox(self, box, data: list) -> None:
        """ clears combo box

        updates combobox so that old content not needed any more isnt displayed and adds 'choose here' dummy
        to ensure an index change will be made (updating next box depends on index change)
        Args:
            box: instance of pyqt5.QtWidgets.qComboBox
            data: data supposed to be inserted into combobox
        Returns:
            None"""

        box.clear()
        box.addItems(["choose here"] + data)

    def validate(self,
                 command,
                 file_path: str = None,
                 schema=None,
                 line_edit_list: list = None,
                 combo_box_list: list = None,
                 data=None,
                 forbidden: list = None,
                 checkfname: bool = None) -> None:
        """validates user input

        Args:
            command: function to be called after vailidation process if finished
            line_edit_list: contents pyqt5.QtWidgets.QlineEdit instances to be checked if empty or current text in forbidden or not in allowed
            combo_box_list: contents pyqt5.QtWidgets.qComboBox instances to be checked if nothing selected
            data: data to be passed into command function if needed
            forbidden: houses keys which are not allowed to be entered
            checkfname: check weather an file path exists or not

        Returns:
            None"""

        fails = 0
        if line_edit_list is not None:
            for x in line_edit_list:
                if x.text() == "":
                    x.setText("fill all fields")
                    fails += 1
                if forbidden is not None and x.text() in forbidden:
                    x.setText("in forbidden!!")
                    fails += 1
        if combo_box_list is not None:
            for combobox in combo_box_list:
                if combobox.currentText() == "":
                    self.statusbar.showMessage("all comboboxes must be filled")
                    fails += 1
        if checkfname is True and self.file_path is None:
            self.statusbar.showMessage(
                "no file path specified, visit Ctrl+o or menuebar/edit/open to fix"
            )
            fails += 1

        if file_path is not None:
            if forbidden is not None and file_path in forbidden:
                fails += 1
                self.statusbar.showMessage("select a file to continue")
            else:
                try:
                    validate_json.validate(file_path, schema)
                except ValidationError as e:
                    self.msg_box = QtWidgets.QMessageBox.critical(
                        self, "validation failed",
                        f"Invalid Json file, problem in: {e.messages}")
                    fails += 1
        if fails == 0:
            if data is None:
                command()
            else:
                command(data)
        else:
            message = f"problem\ncommand: {command.__name__}\nfails: {fails}"
            print(message)
            return message

    def register(self) -> None:
        """registers a new device and saves

        Returns:
            None"""
        logic.register(devname=self.in_devicename.text(),
                       devtype=[
                           device for device in valid_devices
                           if device.__name__ ==
                           self.in_combobox_devicetype.currentText()
                       ].pop(),
                       username=self.in_username.text(),
                       os=self.in_combobox_os.currentText(),
                       comment=self.in_comment.toPlainText(),
                       datetime=str(datetime.datetime.now()),
                       registered_devices=self.registered_devices)

        new_values = [
            self.in_devicename.text(),
            self.in_username.text(),
            self.in_combobox_os.currentText(),
            [
                device.__name__ for device in valid_devices if device.__name__
                == self.in_combobox_devicetype.currentText()
            ].pop(),
            self.in_comment.toPlainText(),
            str(datetime.datetime.now())
        ]
        row = [QStandardItem(str(item)) for item in new_values]
        self.model.appendRow(row)

        self.stacked_widget.setCurrentWidget(self.p_view)
        self.in_devicename.clear()
        self.in_username.clear()
        self.in_combobox_os.clear()
        self.in_comment.clear()
        self.save()

    def delete(self) -> None:
        """deletes all rows associated with min 1 slected cell
        Returns:
            None"""
        rows = sorted(set(index.row()
                          for index in self.table.selectedIndexes()),
                      reverse=True)
        qb = QMessageBox()
        answ = qb.question(self, 'delete rows',
                           f"Are you sure to delete {rows} rows?",
                           qb.Yes | qb.No)

        if answ == qb.Yes:
            for row in rows:
                self.registered_devices.pop(
                    str(self.model.index(row, 0).data()))
                self.model.removeRow(row)
            qb.information(self, 'notification', f"deleted {rows} row")
        else:
            qb.information(self, 'notification', "Nothing Changed")
        self.save()

    def get_open_file_path(self) -> None:
        """gets file-path and set it to self.file_path, extra step for json validation

        Returns:
            None"""

        self.file_path = \
            QFileDialog.getOpenFileName(self, "open file", f"{self.last_open_file_dir or 'c://'}",
                                        "json files (*json)")[0]
        self.validate(command=self.load,
                      file_path=self.file_path,
                      schema=validate_json.ItHilfeDataSchema,
                      forbidden=[""])

    def load(self) -> None:
        """opens json file and loads its content into registered devices

        Returns:
            None"""

        self.model.clear()
        self.registered_devices.clear()
        with open(self.file_path, "r") as file:
            data = dict(json.load(file))
            devices = data["devices"].values()
            self.last_open_file_dir = data["last_open_file_dir"]
            for value in devices:
                row = []
                for i, item in enumerate(value):
                    cell = QStandardItem(str(item))
                    row.append(cell)
                    if i == 0 or i == 3 or i == 5:
                        cell.setEditable(False)
                self.model.appendRow(row)

                new = [x for x in valid_devices
                       if x.__name__ == value[3]].pop(0)(value[0], value[1],
                                                         value[4], value[5])
                new.OS = value[2]
                self.registered_devices[value[0]] = new

        self.model.setHorizontalHeaderLabels(labels)
        self.statusbar.showMessage("")

        # auto complete
        for a in range(len(self.header.editors)):
            completer = QCompleter([
                self.model.data(self.model.index(x, a))
                for x in range(self.model.rowCount())
            ])
            completer.setCompletionMode(QCompleter.InlineCompletion)
            self.header.editors[a].setCompleter(completer)

    def save(self) -> None:
        """saves content fo self.registered_devices into specified json file

        Returns:
            None"""
        if not self.file_path:
            self.statusbar.showMessage(
                "no file path set all changes get lost if closed")
        else:
            with open(
                    self.file_path,
                    'w',
            ) as file:
                devices = {
                    k: [
                        v.name, v.user, v.OS, v.__class__.__name__, v.comment,
                        v.datetime
                    ]
                    for (k, v) in enumerate(self.registered_devices.values())
                }
                last_open_file_dir = "/".join(self.file_path.split("/")[:-1])
                resulting_dict = {
                    "devices": devices,
                    "last_open_file_dir": last_open_file_dir
                }
                json.dump(resulting_dict, file)
                self.statusbar.showMessage("saved file")

        with open(self.user_config_file, "w") as user_preferences_file:
            json.dump(
                {
                    "last_open_file_path": self.last_open_file_path,
                    "initial_theme": self.initial_theme,
                    "font_size": self.text_size_slider.value()
                }, user_preferences_file)

    def new(self, stage: bool, test: bool = False) -> None:
        """creates new csv file to save into

        stage is True: set filepath
        stage is False: set new name, save
        Args:
            stage: determines a which stage to execute this function

        Returns:
            None"""

        if stage is True:
            if not test:
                self.dir = QFileDialog.getExistingDirectory(
                    self, "select a folder", "c://")
            self.stacked_widget.setCurrentWidget(self.p_create)
            self.in_new_filepath.setText(self.dir)
            self.registered_devices.clear()

        else:
            self.file_path = self.dir + f"/{self.in_new_filename.text()}.json"
            self.save()
            self.stacked_widget.setCurrentWidget(self.p_view)

    def print(self, test: bool) -> None:
        """setup and preview pViewTable for paper printing

        Returns:
            None"""

        with open(self.file_path) as f:
            self.data = json.dumps(dict(json.load(f)),
                                   sort_keys=True,
                                   indent=6,
                                   separators=(".", "="))
        self.document = QtWidgets.QTextEdit()
        self.document.setText(self.data)

        if not test:
            printer = QPrinter()
            previewDialog = QPrintPreviewDialog(printer, self)
            previewDialog.paintRequested.connect(
                lambda: self.document.print_(printer))
            previewDialog.exec_()
class AddElementWindow(QDialog):
    def __init__(self, parent):
        super(AddElementWindow, self).__init__()
        self.parent = parent
        self.setWindowTitle("PyEngine - Element")

        self.grid = QGridLayout()

        ltitle = Label("Elements", 15)
        ltitle.setAlignment(Qt.AlignHCenter)
        lname = Label("Nom", 12)
        lname.setAlignment(Qt.AlignHCenter)
        self.nameInput = QLineEdit()
        ltype = Label("Type", 12)
        ltype.setAlignment(Qt.AlignHCenter)
        self.typelist = QListWidget()
        self.typelist.addItems([i for i in types])
        lparent = Label("Parent", 12)
        lparent.setAlignment(Qt.AlignHCenter)
        self.parentlist = QListWidget()
        self.go = QPushButton("Entrer")
        self.cancel = QPushButton("Annuler")
        spacer = QSpacerItem(20, 25)

        self.go.clicked.connect(self.enter)
        self.cancel.clicked.connect(self.close)
        self.typelist.currentItemChanged.connect(self.item_changed)

        self.grid.addWidget(ltitle, 0, 0, 1, 2)
        self.grid.addItem(spacer, 1, 0)
        self.grid.addWidget(lname, 2, 0, 1, 2)
        self.grid.addWidget(self.nameInput, 3, 0, 1, 2)
        self.grid.addItem(spacer, 4, 0)
        self.grid.addWidget(ltype, 5, 0, 1, 2)
        self.grid.addWidget(self.typelist, 6, 0, 1, 2)
        self.grid.addItem(spacer, 7, 0)
        self.grid.addWidget(lparent, 8, 0, 1, 2)
        self.grid.addWidget(self.parentlist, 9, 0, 1, 2)
        self.grid.addWidget(self.go, 10, 0)
        self.grid.addWidget(self.cancel, 10, 1)

        self.grid.setContentsMargins(10, 10, 10, 10)

        self.setLayout(self.grid)
        self.setFixedSize(400, 500)

        self.setWindowFlags(Qt.CustomizeWindowHint)
        self.setWindowFlags(Qt.WindowTitleHint)
        self.setWindowFlags(Qt.WindowCloseButtonHint)

    def enter(self):
        if self.nameInput.text() == "":
            QMessageBox.warning(self, "Erreur",
                                "Le nom de l'élément n'a pas été fourni")
        elif self.typelist.currentItem() is None:
            QMessageBox.warning(self, "Erreur",
                                "Le type de l'élément n'a pas été fourni")
        elif self.parentlist.currentItem() is None:
            QMessageBox.warning(self, "Erreur",
                                "Le parent de l'élément n'a pas été fourni")
        else:
            for v in self.parent.project.all_objects():
                if v.name == self.parentlist.currentItem().text():
                    obj = Object(self.nameInput.text(),
                                 self.typelist.currentItem().text())
                    obj.parent = v
                    obj.set_property("Nom", self.nameInput.text())
                    v.childs.append(obj)
                    break
            self.parent.elements.update_items()
            self.close()

    def item_changed(self, current, previous):
        item = current.text()
        self.parentlist.clear()
        self.parentlist.addItems([
            v.name for v in self.parent.project.all_objects()
            if v.type_ in get_parent_types(item)
        ])
Exemple #16
0
class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("{} {}".format(config.APP_NAME,
                                           config.APP_VERSION))
        self.setMinimumWidth(600)
        self.setStyleSheet(config.STYLESHEET)

        # Widgets
        self.import_label = QLabel(
            'Import a CSV class list to add a new class of reports.')
        self.import_button = QPushButton('Import CSV...')
        self.saved_label = QLabel('You have the following saved classes:')
        self.saved_listwidget = QListWidget(self)
        self.edit_comment_bank_button = QPushButton('Edit Comment Bank')
        self.edit_reports_button = QPushButton('Edit Reports')
        self.export_reports_button = QPushButton('Export Reports')
        self.delete_class_button = QPushButton('Delete')

        # Layout
        self.layout = QVBoxLayout()
        self.layout.addWidget(self.import_label)
        self.layout.addWidget(self.import_button)
        self.layout.addWidget(self.saved_label)
        self.layout.addWidget(self.saved_listwidget)
        self.layout.addWidget(self.edit_comment_bank_button)
        self.layout.addWidget(self.edit_reports_button)
        self.layout.addWidget(self.export_reports_button)
        self.layout.addWidget(self.delete_class_button)

        # Initial run of listbox update
        self.update_saved_list()

        # Slot connections
        self.import_button.clicked.connect(self.import_csv)
        self.saved_listwidget.itemSelectionChanged.connect(
            self.do_update_selection)
        self.edit_comment_bank_button.clicked.connect(self.edit_comment_bank)
        self.edit_reports_button.clicked.connect(self.edit_reports)
        self.export_reports_button.clicked.connect(self.export_reports)
        self.delete_class_button.clicked.connect(self.delete_class)

        self.widget = QWidget()
        self.widget.setLayout(self.layout)

        self.setCentralWidget(self.widget)

    @Slot()
    def import_csv(self):
        # TODO proper validation for input will mean building this dialog out
        # properly using QLineEdit
        subject_name, ok = QInputDialog().getText(
            self, "Subject Name", "Enter a name or code for the subject:")
        if subject_name and ok:
            filename, filt = QFileDialog.getOpenFileName(
                self, "Import CSV", os.path.expanduser("~"),
                "Comma Separated (*.csv)")
            if could_try_harder.import_class_list(filename, subject_name):
                self.update_saved_list()
            else:
                # TODO better error handling here
                print("Import Failed")
                return

    def update_saved_list(self):
        self.saved_listwidget.clear()
        self.saved_listwidget.addItems(could_try_harder.get_saved_list())
        self.do_update_selection()

    @Slot()
    def do_update_selection(self):
        if self.saved_listwidget.selectedItems():
            state = True
        else:
            state = False
        self.edit_comment_bank_button.setEnabled(state)
        self.edit_reports_button.setEnabled(state)
        self.export_reports_button.setEnabled(state)
        self.delete_class_button.setEnabled(state)

    @Slot()
    def edit_comment_bank(self):
        subject_name = self.saved_listwidget.currentItem().text()
        self.edit_comment_bank_window = EditCommentBankWindow(subject_name)
        self.edit_comment_bank_window.show()

    @Slot()
    def edit_reports(self):
        subject_name = self.saved_listwidget.currentItem().text()
        self.edit_reports_window = EditReportsWindow(subject_name)
        self.edit_reports_window.show()

    @Slot()
    def export_reports(self):
        subject_name = self.saved_listwidget.currentItem().text()
        filename, filt = QFileDialog.getSaveFileName(self, "Export",
                                                     os.path.expanduser("~"),
                                                     "Text Files (*.txt)")
        if filename:
            if not could_try_harder.export(subject_name, filename):
                # TODO better error handling here
                print("Export failed.")
                return

    @Slot()
    def delete_class(self):
        confirm_msg = QMessageBox(self)
        confirm_msg.setWindowTitle("Confirm")
        confirm_msg.setText(
            "This will delete the class, comment bankd and reports.")
        confirm_msg.setInformativeText("Continue?")
        confirm_msg.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        confirm_msg.setDefaultButton(QMessageBox.Yes)
        confirm = confirm_msg.exec()
        if confirm == QMessageBox.Yes:
            subject_name = self.saved_listwidget.currentItem().text()
            if not could_try_harder.delete(subject_name):
                # TODO better error handling here
                print("Delete failed.")
            self.update_saved_list()
Exemple #17
0
class EditCommentBankWindow(QMainWindow):
    def __init__(self, subject_name):
        QMainWindow.__init__(self)
        self.setWindowTitle("Edit Comment Bank: {} - {} {}".format(
            subject_name, config.APP_NAME, config.APP_VERSION))
        self.setMinimumWidth(1200)
        self.setStyleSheet(config.STYLESHEET)

        self.subject = could_try_harder.load(subject_name)
        self.saved_list = could_try_harder.get_saved_list()

        # Widgets
        self.intro_comment_label = QLabel("Introductory Comment:")
        self.intro_comment_label.setProperty("styleClass", "heading")
        self.intro_comment_textedit = QTextEdit()
        self.comment_bank_label = QLabel("Comment Bank")
        self.comment_bank_label.setProperty("styleClass", "heading")
        self.comment_bank_listwidget = QListWidget()
        self.placeholder_instructions_label = QLabel(
            config.PLACEHOLDER_INSTRUCTIONS)
        self.add_comment_label = QLabel("Add Comment:")
        self.add_comment_entry = QLineEdit()
        self.add_comment_button = QPushButton("Add")
        self.update_comment_label = QLabel("Update Comment:")
        self.update_comment_entry = QLineEdit()
        self.update_comment_button = QPushButton("Update")
        self.delete_comment_button = QPushButton("Delete Comment")
        self.import_comments_combo = QComboBox()
        self.import_comments_button = QPushButton("Import...")
        self.cancel_button = QPushButton("Cancel")
        self.save_button = QPushButton("Save")

        # Layout
        self.layout = QVBoxLayout()
        self.top_layout = QHBoxLayout()
        self.intro_comment_layout = QVBoxLayout()
        self.intro_comment_layout.addWidget(self.intro_comment_label)
        self.intro_comment_layout.addWidget(self.intro_comment_textedit)
        self.top_layout.addLayout(self.intro_comment_layout)
        self.top_layout.addWidget(self.placeholder_instructions_label)
        self.layout.addLayout(self.top_layout)
        self.middle_layout = QVBoxLayout()
        self.middle_layout.addWidget(self.comment_bank_label)
        self.middle_layout.addWidget(self.comment_bank_listwidget)
        self.comment_actions_layout = QHBoxLayout()
        self.comment_actions_layout.addWidget(self.delete_comment_button, 0,
                                              Qt.AlignLeft)
        self.comment_actions_layout.addWidget(self.import_comments_combo, 1,
                                              Qt.AlignRight)
        self.comment_actions_layout.addWidget(self.import_comments_button, 0,
                                              Qt.AlignRight)
        self.middle_layout.addLayout(self.comment_actions_layout)
        self.update_comment_layout = QGridLayout()
        self.update_comment_layout.addWidget(self.update_comment_label, 0, 0)
        self.update_comment_layout.addWidget(self.update_comment_entry, 0, 1)
        self.update_comment_layout.addWidget(self.update_comment_button, 0, 2)
        self.update_comment_layout.addWidget(self.add_comment_label, 1, 0)
        self.update_comment_layout.addWidget(self.add_comment_entry, 1, 1)
        self.update_comment_layout.addWidget(self.add_comment_button, 1, 2)
        self.middle_layout.addLayout(self.update_comment_layout)
        self.layout.addLayout(self.middle_layout)
        self.bottom_layout = QHBoxLayout()
        self.bottom_layout.addWidget(self.cancel_button, 0, Qt.AlignLeft)
        self.bottom_layout.addWidget(self.save_button, 0, Qt.AlignRight)
        self.layout.addLayout(self.bottom_layout)

        # Slot connections
        self.comment_bank_listwidget.itemSelectionChanged.connect(
            self.do_update_comment_bank_selection)
        self.import_comments_button.clicked.connect(self.do_import_comments)
        self.update_comment_button.clicked.connect(self.do_update_comment)
        self.update_comment_entry.returnPressed.connect(self.do_update_comment)
        self.add_comment_button.clicked.connect(self.do_add_comment)
        self.add_comment_entry.returnPressed.connect(self.do_add_comment)
        self.delete_comment_button.clicked.connect(self.do_delete_comment)
        self.cancel_button.clicked.connect(self.do_cancel)
        self.save_button.clicked.connect(self.do_save)

        # Initial UI update
        self.update_ui()

        self.widget = QWidget()
        self.widget.setLayout(self.layout)
        self.setCentralWidget(self.widget)

    def update_ui(self):
        self.update_import_comments_list()
        self.update_intro_comment()
        self.update_comment_bank()

    def update_import_comments_list(self):
        self.import_comments_combo.clear()
        self.import_comments_combo.insertItems(0, self.saved_list)

    def update_intro_comment(self):
        self.intro_comment_textedit.clear()
        self.intro_comment_textedit.insertPlainText(
            self.subject['intro_comment'])

    def update_comment_bank(self):
        self.comment_bank_listwidget.clear()
        self.comment_bank_listwidget.addItems(self.subject['comment_bank'])
        self.do_update_comment_bank_selection()

    @Slot()
    def do_import_comments(self):
        # TODO confirm dialog first
        confirm_msg = QMessageBox(self)
        confirm_msg.setWindowTitle("Confirm")
        confirm_msg.setText("This will override current comments.")
        confirm_msg.setInformativeText("Do you want to continue?")
        confirm_msg.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        confirm_msg.setDefaultButton(QMessageBox.Yes)
        confirm = confirm_msg.exec()
        if confirm == QMessageBox.Yes:
            if self.import_comments_combo.count() > 0:
                new_subject = could_try_harder.load(
                    self.import_comments_combo.currentText())
                if new_subject:
                    self.subject['intro_comment'] = new_subject[
                        'intro_comment']
                    self.subject['comment_bank'] = new_subject['comment_bank']
                    self.update_ui()
                else:
                    # TODO better error handling here
                    print('Tried to import empty subject.')
                    return
        return

    @Slot()
    def do_update_comment_bank_selection(self):
        if self.comment_bank_listwidget.selectedItems():
            state = True
        else:
            state = False
        self.delete_comment_button.setEnabled(state)
        self.update_comment_button.setEnabled(state)
        # Update the text in the update comment line edit
        self.update_comment_entry.clear()
        if self.comment_bank_listwidget.currentItem():
            self.update_comment_entry.insert(
                self.comment_bank_listwidget.currentItem().text())

    @Slot()
    def do_update_comment(self):
        if self.update_comment_entry.text():
            self.comment_bank_listwidget.currentItem().setText(
                could_try_harder.do_style(
                    self.update_comment_entry.text().strip()))
            self.do_update_comment_bank_selection()

    @Slot()
    def do_add_comment(self):
        if self.add_comment_entry.text():
            self.comment_bank_listwidget.addItem(
                could_try_harder.do_style(
                    self.add_comment_entry.text().strip()))
            self.add_comment_entry.clear()
            self.do_update_comment_bank_selection()

    @Slot()
    def do_delete_comment(self):
        self.comment_bank_listwidget.takeItem(
            self.comment_bank_listwidget.currentRow())
        self.do_update_comment_bank_selection()

    @Slot()
    def do_cancel(self):
        self.close()

    @Slot()
    def do_save(self):
        self.subject['intro_comment'] = could_try_harder.do_style(
            self.intro_comment_textedit.toPlainText().strip())
        self.subject['comment_bank'] = []
        for i in range(self.comment_bank_listwidget.count()):
            self.subject['comment_bank'].append(
                self.comment_bank_listwidget.item(i).text())
        if could_try_harder.save(self.subject):
            self.close()
        else:
            # TODO better error handling here
            print("Save failed.")
Exemple #18
0
class ImportWindow(QDialog):
    def __init__(self):
        super(ImportWindow, self).__init__()

        self.setContentsMargins(5, 5, 5, 5)

        self._gamesdata = []
        self._platforms = []
        self._regions = set()

        self._platformListPath = Path("data/vgdb/")
        self._platformList = []
        for file in self._platformListPath.iterdir():
            self._platformList.append(file.stem)

        self._lblSelect = QLabel("Select platforms to import from:")

        self._consoleList = QListWidget()
        self._consoleList.addItems(sorted(self._platformList))
        self._consoleList.setSelectionMode(QAbstractItemView.MultiSelection)

        self._btnCancel = QPushButton("Cancel")
        self._btnCancel.clicked.connect(self.close)
        self._btnOK = QPushButton("OK")
        self._btnOK.clicked.connect(self._doImport)

        self._hboxOKCancel = QHBoxLayout()
        self._hboxOKCancel.addStretch(5)
        self._hboxOKCancel.addWidget(self._btnOK, 0)
        self._hboxOKCancel.addWidget(self._btnCancel, 1)

        self._vbox = QVBoxLayout()
        self._vbox.addWidget(self._lblSelect, 0)
        self._vbox.addWidget(self._consoleList, 1)
        self._vbox.addLayout(self._hboxOKCancel, 2)

        self.setLayout(self._vbox)
        self.setWindowTitle("Import games")
        self.setFixedSize(500, 280)
        self._center()

    def _center(self):
        """Centers window on screen"""

        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def _doImport(self):
        platforms = [x.text() for x in self._consoleList.selectedItems()]
        proceed = QMessageBox.Ok

        if len(platforms) > 1:
            proceed = QMessageBox.warning(
                self, "Import warning",
                "Importing multiple platforms can take a long time.\n"
                "Are you sure you want to proceed?",
                QMessageBox.Cancel | QMessageBox.Ok, QMessageBox.Cancel)

        if proceed == QMessageBox.Ok:
            newData = []
            for file in self._platformListPath.iterdir():
                if file.stem in platforms:
                    self._platforms.append(file.stem)
                    newData.append(createGameData(file))
            for lst in newData:
                for game in lst:
                    self._gamesdata.append(game)
                    self._regions.add(game["region"])
            self.accept()

    def returnData(self):
        return self._gamesdata, self._platforms, self._regions
Exemple #19
0
class SpecifySearchConditionsDialog(BaseDialog):
    def __init__(self, parent, entity):
        super().__init__(parent, _("Search criteria"), _("&Start search"),
                         _("&Cancel"))
        self._entity = entity
        self._value_widget = None
        self._value_label = None
        self._search_expression_parts = []
        self._added_condition = False
        self._populate_fields_tree(self._entity)

    def create_ui(self):
        fields_label = QLabel(_("Class fields"), self)
        self.layout.addWidget(fields_label, 0, 0)
        self._fields_tree = QTreeWidget(self)
        fields_label.setBuddy(self._fields_tree)
        self._fields_tree.currentItemChanged.connect(
            self.on_fields_tree_sel_changed)
        self.layout.addWidget(self._fields_tree, 1, 0)
        operator_label = QLabel(_("Operator"), self)
        self.layout.addWidget(operator_label, 0, 1)
        self._operator = QListWidget(self)
        operator_label.setBuddy(self._operator)
        self.layout.addWidget(self._operator, 1, 1)
        self._operator.currentRowChanged.connect(self.on_operator_choice)
        self._operator.setCurrentRow(0)
        add_button = QPushButton(_("&Add condition"), self)
        add_button.clicked.connect(self.on_add_clicked)
        self.layout.addWidget(add_button, 2, 0, 1, 3)
        criteria_label = QLabel(_("Current search criteria"), self)
        self.layout.addWidget(criteria_label, 3, 0, 1, 3)
        self._criteria_list = QListWidget(self)
        criteria_label.setBuddy(self._criteria_list)
        self.layout.addWidget(self._criteria_list, 4, 0, 1, 3)
        remove_button = QPushButton(_("&Remove condition"), self)
        remove_button.clicked.connect(self.on_remove_clicked)
        self.layout.addWidget(remove_button, 5, 0, 1, 3)
        distance_label = QLabel(_("Search objects to distance"), self)
        self.layout.addWidget(distance_label, 6, 0)
        self._distance_field = QSpinBox(self)
        self._distance_field.setMaximum(100000)
        self._distance_field.setSuffix(" " + _("meters"))
        self._distance_field.setSpecialValueText(_("No limit"))
        distance_label.setBuddy(self._distance_field)
        self.layout.addWidget(self._distance_field, 6, 1)

    def _populate_fields_tree(self, entity, parent=None):
        if parent is None:
            parent = self._fields_tree.invisibleRootItem()
        metadata = EntityMetadata.for_discriminator(entity)
        for field_name, field in sorted(
                metadata.all_fields.items(),
                key=lambda i: underscored_to_words(i[0])):
            child_metadata = None
            try:
                child_metadata = EntityMetadata.for_discriminator(
                    field.type_name)
            except KeyError:
                pass
            if child_metadata:
                name = get_class_display_name(field.type_name)
                subparent = QTreeWidgetItem([name])
                subparent.setData(0, Qt.UserRole, field_name)
                parent.addChild(subparent)
                self._populate_fields_tree(field.type_name, subparent)
            else:
                item = QTreeWidgetItem([underscored_to_words(field_name)])
                item.setData(0, Qt.UserRole, (field_name, field))
                parent.addChild(item)

    def on_fields_tree_sel_changed(self, item):
        data = item.data(0, Qt.UserRole)
        if data is not None and not isinstance(data, str):
            self._field_name = data[0]
            self._field = data[1]
            self._operators = operators_for_column_class(self._field.type_name)
            self._operator.clear()
            self._operator.addItems([o.label for o in self._operators])
            self._added_condition = False

    def on_operator_choice(self, index):
        self._added_condition = False
        if self._value_widget:
            self.layout.removeWidget(self._value_widget)
            self._value_widget.deleteLater()
        if self._value_label:
            self.layout.removeWidget(self._value_label)
            self._value_label.deleteLater()
            self._value_label = None
        operator = self._operators[self._operator.currentRow()]
        value_label = self._create_value_label(
            operator.get_value_label(self._field))
        self._value_widget = operator.get_value_widget(self, self._field)
        if not self._value_widget:
            return
        QWidget.setTabOrder(self._operator, self._value_widget)
        self._value_label = value_label
        if self._value_label:
            self._value_label.setBuddy(self._value_widget)
            self.layout.addWidget(self._value_label, 0, 2)
        self.layout.addWidget(self._value_widget, 1, 2)

    def _create_value_label(self, label):
        if not label:
            return
        label = QLabel(label, self)
        return label

    def on_add_clicked(self, evt):
        if not hasattr(self, "_field_name"):
            return
        self._added_condition = True
        json_path = []
        parent_item = self._fields_tree.currentItem().parent()
        parent_data = parent_item.data(0, Qt.UserRole) if parent_item else None
        if isinstance(parent_data, str):
            json_path.append(parent_data)
        json_path.append(self._field_name)
        json_path = ".".join(json_path)
        operator_obj = self._operators[self._operator.currentRow()]
        expression = operator_obj.get_comparison_expression(
            self._field, FieldNamed(json_path), self._value_widget)
        self._search_expression_parts.append(expression)
        self._criteria_list.addItem(
            f"{underscored_to_words(self._field_name)} {operator_obj.label} {operator_obj.get_value_as_string(self._field, self._value_widget)}"
        )

    @property
    def distance(self):
        return self._distance_field.value()

    def create_conditions(self):
        conditions = []
        if self._search_expression_parts:
            for part in self._search_expression_parts:
                conditions.append(part)
        return conditions

    def on_remove_clicked(self, evt):
        selection = self._criteria_list.currentRow()
        if selection < 0:
            return
        del self._search_expression_parts[selection]
        self._criteria_list.removeItemWidget(self._criteria_list.currentItem())

    def ok_clicked(self):
        if not self._added_condition:
            if QMessageBox.question(
                    self, _("Question"),
                    _("It appears that you forgot to add the current condition to the conditions list. Do you want to add it before starting the search?"
                      )):
                self.on_add_clicked(None)
        super().ok_clicked()
Exemple #20
0
class DirectorySetChooseDialog(QDialog):
    """ simple dialog to let user choose from the available directory sets """
    def __init__(self, parent, set_pieces_and_playlist_function):
        """ pretty standard constructor: set up class variables, ui elements
            and layout parameters:
                - parent: parent widget of this dialog
                - set_pieces_and_playlist_function: is called to set the pieces
                  and playlist variables of the parent """

        super(DirectorySetChooseDialog, self).__init__(parent)

        # -- create and setup ui elements --
        self._lbl_prompt = QLabel(
            'Please choose one or more from the available directory sets:')
        self._lbl_prompt.setMaximumHeight(30)
        self._listwidget_sets = QListWidget()
        self._checkbox_shuffle = QCheckBox('Shuffle playlist')
        self._checkbox_shuffle.setChecked(True)  # shuffling enabled by default
        self._listwidget_sets.itemDoubleClicked.connect(self.__action_choose)
        self._btn_choose = QPushButton('Choose')
        self._btn_choose.clicked.connect(self.__action_choose)

        # -- setup _listwidget_sets
        options = listdir('../directories')  # get list of available sets
        options.remove('Default.txt')  # remove default entry
        # and reinsert at index 0 to ensure that 'Default' is the first option
        options.insert(0, 'Default.txt')
        self._listwidget_sets.addItems([s[:-4] for s in options])
        self._listwidget_sets.setSelectionMode(  # allow multiple selections
            QAbstractItemView.ExtendedSelection)
        self._listwidget_sets.setCurrentRow(0)  # select default entry

        # -- create layout --
        self._layout = QGridLayout(self)
        self._layout.addWidget(self._lbl_prompt, 0, 0, 1, -1)
        self._layout.addWidget(self._listwidget_sets, 1, 0, 4, -1)
        self._layout.addWidget(self._checkbox_shuffle, 5, 0, 1, -1)
        self._layout.addWidget(self._btn_choose, 6, 0, 1, -1)

        # -- various setup --
        self._set_pieces_and_playlist = set_pieces_and_playlist_function
        self.setModal(True)
        self.setWindowTitle('Please choose a directory set')
        self.setMinimumWidth(600)
        self.setMinimumHeight(400)

    def __action_choose(self):
        """ (gets called when self._btn_choose is clicked)
            loads pieces from selected sets, creates a playlist
            (shuffled if wanted) and calls self._set_pieces_and_playlist """

        selected_sets = self._listwidget_sets.selectedItems()
        selected_str = 'Currently loaded directory set(s):\n"' + \
            '", "'.join([s.text() for s in selected_sets]) + '"'
        pieces = get_pieces_from_sets(
            [s.text() + '.txt' for s in selected_sets])
        playlist = list(pieces.keys())  # must be a list to be shuffled
        shuffled = self._checkbox_shuffle.isChecked()
        if shuffled:
            shuffle(playlist)
        self._set_pieces_and_playlist(pieces, playlist, selected_str, shuffled)
        self.close()
Exemple #21
0
class PiecesPlayer(QWidget):
    """ main widget of application (used as widget inside PiecesMainWindow) """
    def __init__(self, parent):
        """ standard constructor: set up class variables, ui elements
            and layout """

        # TODO: split current piece info into separate lineedits for title, album name and length
        # TODO: make time changeable by clicking next to the slider (not only
        #       by dragging the slider)
        # TODO: add "about" action to open info dialog in new "help" menu
        # TODO: add option to loop current piece (?)
        # TODO: more documentation
        # TODO: add some "whole piece time remaining" indicator? (complicated)
        # TODO: implement a playlist of pieces that can be edited and enable
        #       going back to the previous piece (also un- and re-shuffling?)
        # TODO: implement debug dialog as menu action (if needed)

        if not isinstance(parent, PiecesMainWindow):
            raise ValueError('Parent widget must be a PiecesMainWindow')

        super(PiecesPlayer, self).__init__(parent=parent)

        # -- declare and setup variables for storing information --
        # various data
        self._set_str = ''  # string of currently loaded directory sets
        self._pieces = {}  # {<piece1>: [<files piece1 consists of>], ...}
        self._playlist = []  # list of keys of self._pieces (determines order)
        self._shuffled = True  # needed for (maybe) reshuffling when looping
        # doc for self._history:
        # key: timestamp ('HH:MM:SS'),
        # value: info_str of piece that started playing at that time
        self._history = {}
        self._status = 'Paused'
        self._current_piece = {'title': '', 'files': [], 'play_next': 0}
        self._default_volume = 60  # in percent from 0 - 100
        self._volume_before_muted = self._default_volume
        # set to true by self.__event_movement_ended and used by self.__update
        self._skip_to_next = False
        # vlc-related variables
        self._vlc_instance = VLCInstance()
        self._vlc_mediaplayer = self._vlc_instance.media_player_new()
        self._vlc_mediaplayer.audio_set_volume(self._default_volume)
        self._vlc_medium = None
        self._vlc_events = self._vlc_mediaplayer.event_manager()

        # -- create and setup ui elements --
        # buttons
        self._btn_play_pause = QPushButton(QIcon(get_icon_path('play')), '')
        self._btn_previous = QPushButton(QIcon(get_icon_path('previous')), '')
        self._btn_next = QPushButton(QIcon(get_icon_path('next')), '')
        self._btn_volume = QPushButton(QIcon(get_icon_path('volume-high')), '')
        self._btn_loop = QPushButton(QIcon(get_icon_path('loop')), '')
        self._btn_loop.setCheckable(True)
        self._btn_play_pause.clicked.connect(self.__action_play_pause)
        self._btn_previous.clicked.connect(self.__action_previous)
        self._btn_next.clicked.connect(self.__action_next)
        self._btn_volume.clicked.connect(self.__action_volume_clicked)
        # labels
        self._lbl_current_piece = QLabel('Current piece:')
        self._lbl_movements = QLabel('Movements:')
        self._lbl_time_played = QLabel('00:00')
        self._lbl_time_left = QLabel('-00:00')
        self._lbl_volume = QLabel('100%')
        # needed so that everything has the same position
        # independent of the number of digits of volume
        self._lbl_volume.setMinimumWidth(55)
        # sliders
        self._slider_time = QSlider(Qt.Horizontal)
        self._slider_volume = QSlider(Qt.Horizontal)
        self._slider_time.sliderReleased.connect(
            self.__event_time_changed_by_user)
        self._slider_volume.valueChanged.connect(self.__event_volume_changed)
        self._slider_time.setRange(0, 100)
        self._slider_volume.setRange(0, 100)
        self._slider_volume.setValue(self._default_volume)
        self._slider_volume.setMinimumWidth(100)
        # other elements
        self._checkbox_loop_playlist = QCheckBox('Loop playlist')
        self._lineedit_current_piece = QLineEdit()
        self._lineedit_current_piece.setReadOnly(True)
        self._lineedit_current_piece.textChanged.connect(
            self.__event_piece_text_changed)
        self._listwidget_movements = QListWidget()
        self._listwidget_movements.itemClicked.connect(
            self.__event_movement_selected)

        # -- create layout and insert ui elements--
        self._layout = QVBoxLayout(self)
        # row 0 (name of current piece)
        self._layout_piece_name = QHBoxLayout()
        self._layout_piece_name.addWidget(self._lbl_current_piece)
        self._layout_piece_name.addWidget(self._lineedit_current_piece)
        self._layout.addLayout(self._layout_piece_name)
        # rows 1 - 5 (movements of current piece)
        self._layout.addWidget(self._lbl_movements)
        self._layout.addWidget(self._listwidget_movements)
        # row 6 (time)
        self._layout_time = QHBoxLayout()
        self._layout_time.addWidget(self._lbl_time_played)
        self._layout_time.addWidget(self._slider_time)
        self._layout_time.addWidget(self._lbl_time_left)
        self._layout.addLayout(self._layout_time)
        # row 7 (buttons and volume)
        self._layout_buttons_and_volume = QHBoxLayout()
        self._layout_buttons_and_volume.addWidget(self._btn_play_pause)
        self._layout_buttons_and_volume.addWidget(self._btn_previous)
        self._layout_buttons_and_volume.addWidget(self._btn_next)
        self._layout_buttons_and_volume.addWidget(self._btn_loop)
        self._layout_buttons_and_volume.addSpacing(40)
        # distance between loop and volume buttons: min. 40, but stretchable
        self._layout_buttons_and_volume.addStretch()
        self._layout_buttons_and_volume.addWidget(self._btn_volume)
        self._layout_buttons_and_volume.addWidget(self._slider_volume)
        self._layout_buttons_and_volume.addWidget(self._lbl_volume)
        self._layout.addLayout(self._layout_buttons_and_volume)

        # -- setup hotkeys --
        self._KEY_CODES_PLAY_PAUSE = [269025044]
        self._KEY_CODES_NEXT = [269025047]
        self._KEY_CODES_PREVIOUS = [269025046]
        self._keyboard_listener = keyboard.Listener(on_press=self.__on_press)
        self._keyboard_listener.start()
        QShortcut(QKeySequence('Space'), self, self.__action_play_pause)

        # -- various setup --
        self._timer = QTimer(self)
        self._timer.timeout.connect(self.__update)
        self._timer.start(100)  # update every 100ms
        self.setMinimumWidth(900)
        self.setMinimumHeight(400)
        # get directory set(s) input and set up self._pieces
        # (exec_ means we'll wait for the user input before continuing)
        DirectorySetChooseDialog(self, self.set_pieces_and_playlist).exec_()
        # skip to next movement / next piece when current one has ended
        self._vlc_events.event_attach(VLCEventType.MediaPlayerEndReached,
                                      self.__event_movement_ended)

    def __action_next(self):
        """ switches to next file in self._current_piece['files']
            or to the next piece, if the current piece has ended """

        reset_pause_after_current = False

        # current movement is last of the current piece
        if self._current_piece['play_next'] == -1:
            if len(self._playlist) == 0:  # reached end of playlist
                if self._btn_loop.isChecked():
                    self._playlist = list(self._pieces.keys())
                    if self._shuffled:
                        shuffle(self._playlist)
                    return

                if self._status == 'Playing':
                    self.__action_play_pause()
                self._current_piece['title'] = ''
                self._current_piece['files'] = []
                self._current_piece['play_next'] = -1
                self._lineedit_current_piece.setText('')
                self.__update_movement_list()
                self.parentWidget().update_status_bar(
                    self._status, 'End of playlist reached.')
                return
            else:
                if self.parentWidget().get_exit_after_current():
                    self.parentWidget().exit()
                if self.parentWidget().get_pause_after_current():
                    self.__action_play_pause()
                    reset_pause_after_current = True
                    # reset of the menu action will be at the end of this
                    # function, or else we won't stay paused

                self._current_piece['title'] = self._playlist.pop(0)
                self._current_piece['files'] = [
                    p[1:-1] for p in self._pieces[self._current_piece['title']]
                ]
                # some pieces only have one movement
                self._current_piece['play_next'] = \
                    1 if len(self._current_piece['files']) > 1 else -1
                self.__update_vlc_medium(0)
                self._lineedit_current_piece.setText(
                    create_info_str(self._current_piece['title'],
                                    self._current_piece['files']))
                self.__update_movement_list()
                self._history[datetime.now().strftime('%H:%M:%S')] = \
                    self._lineedit_current_piece.text()
        else:
            self.__update_vlc_medium(self._current_piece['play_next'])
            # next is last movement
            if self._current_piece['play_next'] == \
               len(self._current_piece['files']) - 1:
                self._current_piece['play_next'] = -1
            else:  # there are at least two movements of current piece left
                self._current_piece['play_next'] += 1
        if self._status == 'Paused' and not reset_pause_after_current:
            self.__action_play_pause()
        elif reset_pause_after_current:
            self.parentWidget().set_pause_after_current(False)
        else:
            self._vlc_mediaplayer.play()
        self.parentWidget().update_status_bar(
            self._status,
            f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}')

    def __action_play_pause(self):
        """ (gets called when self._btn_play_pause is clicked)
            toggles playing/pausing music and updates everything as needed """

        # don't do anything now (maybe end of playlist reached?)
        if self._current_piece['title'] == '':
            return

        if self._status == 'Paused':
            if not self._vlc_medium:
                self.__action_next()
            self._vlc_mediaplayer.play()
            self._btn_play_pause.setIcon(QIcon(get_icon_path('pause')))
            self._status = 'Playing'
        else:
            self._vlc_mediaplayer.pause()
            self._btn_play_pause.setIcon(QIcon(get_icon_path('play')))
            self._status = 'Paused'
        self.parentWidget().update_status_bar(
            self._status,
            f'{len(self._pieces) - len(self._playlist)}/{len(self._pieces)}')

    def __action_previous(self):
        """ (called when self._btn_previous ist clicked)
            goes back one movement of the current piece, if possible
            (cannot go back to previous piece) """

        # can't go back to previous piece, but current one has no or one movement
        if len(self._current_piece['files']) <= 1:
            pass
        # currently playing first movement, so nothing to do as well
        elif self._current_piece['play_next'] == 1:
            pass
        else:  # we can go back one movement
            # currently at last movement
            if self._current_piece['play_next'] == -1:
                # set play_next to last movement
                self._current_piece['play_next'] = \
                    len(self._current_piece['files']) - 1
            else:  # currently before last movement
                # set play_next to current movement
                self._current_piece['play_next'] -= 1
            self._vlc_mediaplayer.stop()
            self.__update_vlc_medium(self._current_piece['play_next'] - 1)
            self._vlc_mediaplayer.play()

    def __action_volume_clicked(self):
        """ (called when self._btn_volume is clicked)
            (un)mutes volume """

        if self._slider_volume.value() == 0:  # unmute volume
            self._slider_volume.setValue(self._volume_before_muted)
        else:  # mute volume
            self._volume_before_muted = self._slider_volume.value()
            self._slider_volume.setValue(0)

    def __event_movement_ended(self, event):
        """ (called when self._vlc_media_player emits a MediaPlayerEndReached
            event)
            sets self._skip_to_next to True so the next self.__update call
            will trigger self.__action_next """

        self._skip_to_next = True

    def __event_movement_selected(self):
        """ (called when self._listwidget_movements emits itemClicked)
            skips to the newly selected movement """

        index = self._listwidget_movements.indexFromItem(
            self._listwidget_movements.currentItem()).row()
        # user selected a movement different from the current one
        if index != self.__get_current_movement_index():
            self._current_piece['play_next'] = index
            self.__action_next()

    def __event_piece_text_changed(self):
        """ (called when self._lineedit_current_piece emits textChanged)
            ensures that the user sees the beginning of the text in
            self._lineedit_current_piece (if text is too long, the end will be
            cut off and the user must scroll manually to see it) """

        self._lineedit_current_piece.setCursorPosition(0)

    def __event_volume_changed(self):
        """ (called when value of self._slider_volume changes)
            updates text of self._lbl_volume to new value of self._slider_value
            and sets icon of self._btn_volume to a fitting one """

        volume = self._slider_volume.value()
        self._lbl_volume.setText(f'{volume}%')
        if volume == 0:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-muted')))
        elif volume < 34:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-low')))
        elif volume < 67:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-medium')))
        else:
            self._btn_volume.setIcon(QIcon(get_icon_path('volume-high')))
        self._vlc_mediaplayer.audio_set_volume(volume)

    def __event_time_changed_by_user(self):
        """ (called when user releases self._slider_time)
            synchronizes self._vlc_mediaplayer's position to the new value
            of self._slider_time """

        self._vlc_mediaplayer.set_position(self._slider_time.value() / 100)

    def __get_current_movement_index(self):
        """ returns the index of the current movement in
            self._current_piece['files'] """

        play_next = self._current_piece['play_next']
        if play_next == -1:
            return len(self._current_piece['files']) - 1
        else:
            return play_next - 1

    def __on_press(self, key):
        """ (called by self._keyboard_listener when a key is pressed)
            looks up key code corresponding to key and calls the appropriate
            action function """

        try:  # key is not always of the same type (why would it be?!)
            key_code = key.vk
        except AttributeError:
            key_code = key.value.vk
        if key_code in self._KEY_CODES_PLAY_PAUSE:
            self.__action_play_pause()
        elif key_code in self._KEY_CODES_NEXT:
            self.__action_next()
        elif key_code in self._KEY_CODES_PREVIOUS:
            self.__action_previous()

    def __update_movement_list(self):
        """ removes all items currently in self._listwidget_movements and adds
            everything in self._current_piece['files] """

        # TODO: use ID3 title instead of filename

        while self._listwidget_movements.count() > 0:
            self._listwidget_movements.takeItem(0)
        files = self._current_piece['files']
        if os_name == 'nt':  # windows paths look different than posix paths
            # remove path to file, title number and .mp3 ending
            files = [i[i.rfind('\\') + 3:-4] for i in files]
        else:
            files = [i[i.rfind('/') + 4:-4] for i in files]
        self._listwidget_movements.addItems(files)

    def __update(self):
        """ (periodically called when self._timer emits timeout signal)
            updates various ui elements"""

        # -- select currently playing movement in self._listwidget_movements --
        if self._listwidget_movements.count() > 0:
            self._listwidget_movements.item(
                self.__get_current_movement_index()).setSelected(True)

        # -- update text of self._lbl_time_played and self._lbl_time_left,
        # if necessary --
        if self._vlc_medium:
            try:
                time_played = self._vlc_mediaplayer.get_time()
                medium_duration = self._vlc_medium.get_duration()
                # other values don't make sense (but do occur)
                if (time_played >= 0) and (time_played <= medium_duration):
                    self._lbl_time_played.setText(
                        get_time_str_from_ms(time_played))
                else:
                    self._lbl_time_played.setText(get_time_str_from_ms(0))
                self._lbl_time_left.setText(
                    f'-{get_time_str_from_ms(medium_duration - time_played)}')
            except OSError:  # don't know why that occurs sometimes
                pass

        # -- update value of self._slider_time --
        # don't reset slider to current position if user is dragging it
        if not self._slider_time.isSliderDown():
            try:
                self._slider_time.setValue(
                    self._vlc_mediaplayer.get_position() * 100)
            except OSError:  # don't know why that occurs sometimes
                pass

        if self._skip_to_next:
            self._skip_to_next = False
            self.__action_next()

    def __update_vlc_medium(self, files_index):
        old_medium = self._vlc_medium
        self._vlc_medium = self._vlc_instance.media_new(
            self._current_piece['files'][files_index])
        self._vlc_medium.parse()
        self._vlc_mediaplayer.set_media(self._vlc_medium)
        if old_medium:  # only release if not None
            old_medium.release()

    def get_history(self):
        """ getter function for parent widget """

        return self._history

    def get_set_str(self):
        """ getter function for parent widget """

        return self._set_str if self._set_str != '' \
            else 'No directory set loaded.'

    def set_pieces_and_playlist(self, pieces, playlist, set_str, shuffled):
        """ needed so that DirectorySetChooseDialog can set our self._pieces
            and self._playlist """

        # just to be sure
        if isinstance(pieces, dict) and isinstance(playlist, list):
            self._vlc_mediaplayer.stop()
            self._set_str = set_str
            self._pieces = pieces
            self._playlist = playlist
            self._shuffled = shuffled
            self._current_piece['title'] = self._playlist.pop(0)
            self._current_piece['files'] = [
                p.replace('"', '')
                for p in self._pieces[self._current_piece['title']]
            ]
            self._current_piece['play_next'] = \
                1 if len(self._current_piece['files']) > 1 else -1
            self._lineedit_current_piece.setText(
                create_info_str(self._current_piece['title'],
                                self._current_piece['files']))
            self.__update_movement_list()
            self.__update_vlc_medium(0)
            self._history[datetime.now().strftime('%H:%M:%S')] = \
                self._lineedit_current_piece.text()

    def exit(self):
        """ exits cleanly """

        try:  # don't know why that occurs sometimes
            self._vlc_mediaplayer.stop()
            self._vlc_mediaplayer.release()
            self._vlc_instance.release()
        except OSError:
            pass

        self._keyboard_listener.stop()
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        self.textEdit = QTextEdit()
        self.setCentralWidget(self.textEdit)

        self.createActions()
        self.createMenus()
        self.createToolBars()
        self.createStatusBar()
        self.createDockWindows()

        self.setWindowTitle("Dock Widgets")

        self.newLetter()

    def newLetter(self):
        self.textEdit.clear()

        cursor = self.textEdit.textCursor()
        cursor.movePosition(QTextCursor.Start)
        topFrame = cursor.currentFrame()
        topFrameFormat = topFrame.frameFormat()
        topFrameFormat.setPadding(16)
        topFrame.setFrameFormat(topFrameFormat)

        textFormat = QTextCharFormat()
        boldFormat = QTextCharFormat()
        boldFormat.setFontWeight(QFont.Bold)
        italicFormat = QTextCharFormat()
        italicFormat.setFontItalic(True)

        tableFormat = QTextTableFormat()
        tableFormat.setBorder(1)
        tableFormat.setCellPadding(16)
        tableFormat.setAlignment(Qt.AlignRight)
        cursor.insertTable(1, 1, tableFormat)
        cursor.insertText("The Firm", boldFormat)
        cursor.insertBlock()
        cursor.insertText("321 City Street", textFormat)
        cursor.insertBlock()
        cursor.insertText("Industry Park")
        cursor.insertBlock()
        cursor.insertText("Some Country")
        cursor.setPosition(topFrame.lastPosition())
        cursor.insertText(QDate.currentDate().toString("d MMMM yyyy"),
                          textFormat)
        cursor.insertBlock()
        cursor.insertBlock()
        cursor.insertText("Dear ", textFormat)
        cursor.insertText("NAME", italicFormat)
        cursor.insertText(",", textFormat)
        for i in range(3):
            cursor.insertBlock()
        cursor.insertText("Yours sincerely,", textFormat)
        for i in range(3):
            cursor.insertBlock()
        cursor.insertText("The Boss", textFormat)
        cursor.insertBlock()
        cursor.insertText("ADDRESS", italicFormat)

    def print_(self):
        document = self.textEdit.document()
        printer = QPrinter()

        dlg = QPrintDialog(printer, self)
        if dlg.exec_() != QDialog.Accepted:
            return

        document.print_(printer)

        self.statusBar().showMessage("Ready", 2000)

    def save(self):
        filename, _ = QFileDialog.getSaveFileName(self, "Choose a file name",
                                                  '.', "HTML (*.html *.htm)")
        if not filename:
            return

        file = QFile(filename)
        if not file.open(QFile.WriteOnly | QFile.Text):
            QMessageBox.warning(
                self, "Dock Widgets",
                "Cannot write file %s:\n%s." % (filename, file.errorString()))
            return

        out = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        out << self.textEdit.toHtml()
        QApplication.restoreOverrideCursor()

        self.statusBar().showMessage("Saved '%s'" % filename, 2000)

    def undo(self):
        document = self.textEdit.document()
        document.undo()

    def insertCustomer(self, customer):
        if not customer:
            return
        customerList = customer.split(', ')
        document = self.textEdit.document()
        cursor = document.find('NAME')
        if not cursor.isNull():
            cursor.beginEditBlock()
            cursor.insertText(customerList[0])
            oldcursor = cursor
            cursor = document.find('ADDRESS')
            if not cursor.isNull():
                for i in customerList[1:]:
                    cursor.insertBlock()
                    cursor.insertText(i)
                cursor.endEditBlock()
            else:
                oldcursor.endEditBlock()

    def addParagraph(self, paragraph):
        if not paragraph:
            return
        document = self.textEdit.document()
        cursor = document.find("Yours sincerely,")
        if cursor.isNull():
            return
        cursor.beginEditBlock()
        cursor.movePosition(QTextCursor.PreviousBlock, QTextCursor.MoveAnchor,
                            2)
        cursor.insertBlock()
        cursor.insertText(paragraph)
        cursor.insertBlock()
        cursor.endEditBlock()

    def about(self):
        QMessageBox.about(
            self, "About Dock Widgets",
            "The <b>Dock Widgets</b> example demonstrates how to use "
            "Qt's dock widgets. You can enter your own text, click a "
            "customer to add a customer name and address, and click "
            "standard paragraphs to add them.")

    def createActions(self):
        self.newLetterAct = QAction(QIcon.fromTheme('document-new',
                                                    QIcon(':/images/new.png')),
                                    "&New Letter",
                                    self,
                                    shortcut=QKeySequence.New,
                                    statusTip="Create a new form letter",
                                    triggered=self.newLetter)

        self.saveAct = QAction(QIcon.fromTheme('document-save',
                                               QIcon(':/images/save.png')),
                               "&Save...",
                               self,
                               shortcut=QKeySequence.Save,
                               statusTip="Save the current form letter",
                               triggered=self.save)

        self.printAct = QAction(QIcon.fromTheme('document-print',
                                                QIcon(':/images/print.png')),
                                "&Print...",
                                self,
                                shortcut=QKeySequence.Print,
                                statusTip="Print the current form letter",
                                triggered=self.print_)

        self.undoAct = QAction(QIcon.fromTheme('edit-undo',
                                               QIcon(':/images/undo.png')),
                               "&Undo",
                               self,
                               shortcut=QKeySequence.Undo,
                               statusTip="Undo the last editing action",
                               triggered=self.undo)

        self.quitAct = QAction("&Quit",
                               self,
                               shortcut="Ctrl+Q",
                               statusTip="Quit the application",
                               triggered=self.close)

        self.aboutAct = QAction("&About",
                                self,
                                statusTip="Show the application's About box",
                                triggered=self.about)

        self.aboutQtAct = QAction("About &Qt",
                                  self,
                                  statusTip="Show the Qt library's About box",
                                  triggered=QApplication.instance().aboutQt)

    def createMenus(self):
        self.fileMenu = self.menuBar().addMenu("&File")
        self.fileMenu.addAction(self.newLetterAct)
        self.fileMenu.addAction(self.saveAct)
        self.fileMenu.addAction(self.printAct)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.quitAct)

        self.editMenu = self.menuBar().addMenu("&Edit")
        self.editMenu.addAction(self.undoAct)

        self.viewMenu = self.menuBar().addMenu("&View")

        self.menuBar().addSeparator()

        self.helpMenu = self.menuBar().addMenu("&Help")
        self.helpMenu.addAction(self.aboutAct)
        self.helpMenu.addAction(self.aboutQtAct)

    def createToolBars(self):
        self.fileToolBar = self.addToolBar("File")
        self.fileToolBar.addAction(self.newLetterAct)
        self.fileToolBar.addAction(self.saveAct)
        self.fileToolBar.addAction(self.printAct)

        self.editToolBar = self.addToolBar("Edit")
        self.editToolBar.addAction(self.undoAct)

    def createStatusBar(self):
        self.statusBar().showMessage("Ready")

    def createDockWindows(self):
        dock = QDockWidget("Customers", self)
        dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
        self.customerList = QListWidget(dock)
        self.customerList.addItems(
            ("John Doe, Harmony Enterprises, 12 Lakeside, Ambleton",
             "Jane Doe, Memorabilia, 23 Watersedge, Beaton",
             "Tammy Shea, Tiblanka, 38 Sea Views, Carlton",
             "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal",
             "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston",
             "Sally Hobart, Tiroli Tea, 67 Long River, Fedula"))
        dock.setWidget(self.customerList)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        dock = QDockWidget("Paragraphs", self)
        self.paragraphsList = QListWidget(dock)
        self.paragraphsList.addItems(
            ("Thank you for your payment which we have received today.",
             "Your order has been dispatched and should be with you within "
             "28 days.",
             "We have dispatched those items that were in stock. The rest of "
             "your order will be dispatched once all the remaining items "
             "have arrived at our warehouse. No additional shipping "
             "charges will be made.",
             "You made a small overpayment (less than $5) which we will keep "
             "on account for you, or return at your request.",
             "You made a small underpayment (less than $1), but we have sent "
             "your order anyway. We'll add this underpayment to your next "
             "bill.",
             "Unfortunately you did not send enough money. Please remit an "
             "additional $. Your order will be dispatched as soon as the "
             "complete amount has been received.",
             "You made an overpayment (more than $5). Do you wish to buy more "
             "items, or should we return the excess to you?"))
        dock.setWidget(self.paragraphsList)
        self.addDockWidget(Qt.RightDockWidgetArea, dock)
        self.viewMenu.addAction(dock.toggleViewAction())

        self.customerList.currentTextChanged.connect(self.insertCustomer)
        self.paragraphsList.currentTextChanged.connect(self.addParagraph)
Exemple #23
0
class MyGui(QWidget, GitCmd):
    def __init__(self, title="Create By ZZZ", path=DEFAULT_GIT_REPO):
        super(MyGui, self).__init__()
        GitCmd.__init__(self, path)  # 初始化命令
        self.show_info = None
        self.cmd_history = None
        self.cmd_history_cmd_list = list()
        # 复选框
        self.check_box_select_all = None
        self.check_box = list()
        self.check_box_button = None
        # 默认值
        self.show_log_num = DEFAULT_GIT_LOG_NUM
        self.file_list = DEFAULT_GIT_ADD_LIST
        self.gridLayout = QGridLayout(self)
        self.setGeometry(100, 100, 800, 700)
        self.setWindowTitle(title)
        # 记录当前widget最大行号
        self.max_line_no = 0

    # - - - - - - - - -  - - - - - - - - -  - - - - - - - - -  - - - - - - - - -
    # 显示信息相关
    def add_info_text(self):
        # 添加消息显示框,我们使用列表方式显示
        self.show_info = QListWidget()
        self.show_info.setFixedSize(800, 600)
        self.gridLayout.addWidget(self.show_info, 0, 0, 1, 4)
        # 单击触发绑定的槽函数
        self.show_info.itemClicked.connect(self.text_block_clicked)

        # 命令行显示,我们使用下拉框
        self.cmd_history = QComboBox()
        self.gridLayout.addWidget(self.cmd_history, 1, 0, 1, 4)
        # 设置行号
        self.max_line_no = 2

    def add_cmd(self, cmd):
        if len(self.cmd_history_cmd_list) < 10:
            self.cmd_history_cmd_list.append(cmd)
        else:
            self.cmd_history_cmd_list = self.cmd_history_cmd_list[1:].append(cmd)
        self.cmd_history.clear()
        self.cmd_history.addItems(reversed(self.cmd_history_cmd_list))

    def update_cmd(self, extra: str):
        cur_cmd = self.cmd_history.currentText().split(" ")
        new_cmd = cur_cmd[:2] + [extra]  # 拆分出来git和后面的命令,拼接参数
        if cur_cmd:
            self.cmd_history.setItemText(0, " ".join(new_cmd))

    # 对每一行都进行上色
    def my_print(self, text_list: list, background: list):
        # self.show_info.clear() 这个可以清空显示
        self.show_info.addItems([".  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  . .  .  .  .  .  .  .  .  "
                                 ".  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  . .  .  .  .  .  .  .  .  "])
        for ind in range(len(text_list)):
            item = QListWidgetItem('%s' % text_list[ind])
            item.setBackground(QColor(background[ind]))  # 上色
            self.show_info.addItem(item)
        self.show_info.scrollToBottom()  # 自动到最后行

    @staticmethod
    def text_block_clicked(item):
        # 这里没有什么要做的
        # QMessageBox.information(self, "命令历史", "你选择了: " + item.text())
        pass
    # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    # - - - - - - - - -  - - - - - - - - -  - - - - - - - - -  - - - - - - - - -
    # 添加按钮
    def add_git_button(self):
        cmd_add = QPushButton('Git Add')
        cmd_reset = QPushButton('Git Reset')
        cmd_status = QPushButton('Git Status')
        cmd_log = QPushButton('Git Log')
        cmd_run = QPushButton('Run Cmd')
        line_no = self.max_line_no
        self.gridLayout.addWidget(cmd_add, line_no, 0)
        self.gridLayout.addWidget(cmd_reset, line_no, 1)
        self.gridLayout.addWidget(cmd_status, line_no, 2)
        self.gridLayout.addWidget(cmd_log, line_no, 3)
        self.gridLayout.addWidget(cmd_run, line_no + 1, 0)
        self.max_line_no = self.max_line_no + 2
        cmd_log.clicked.connect(self.git_log)
        cmd_add.clicked.connect(self.git_add)
        cmd_reset.clicked.connect(self.git_reset)
        cmd_status.clicked.connect(self.git_status)
        cmd_run.clicked.connect(self.run_git)

    def run_git(self):
        cur_cmd = self.cmd_history.currentText()
        # 执行代码
        if cur_cmd.startswith("git add"):
            result, background = self.cmd_git_add(self.file_list)
            self.my_print(result, background)
        elif cur_cmd.startswith("git status"):
            result, background = self.cmd_git_status()
            self.my_print(result, background)
        elif cur_cmd.startswith("git log"):
            result, background = self.cmd_git_log(self.show_log_num)
            self.my_print(result, background)
        elif cur_cmd.startswith("git reset"):
            result, background = self.cmd_git_reset(self.file_list)
            self.my_print(result, background)

    def git_log(self):
        # 日常清理
        self.cleanup()
        # 记录命令
        self.add_cmd("git log -" + self.show_log_num)
        # 可以直接运行
        self.run_git()

    def git_add(self):
        # 日常清理
        self.cleanup()
        # 文件选择
        files = self.get_file_list_by_status()
        self.create_check_box(files)
        # 记录命令
        self.add_cmd("git add .")

    def git_status(self):
        # 日常清理
        self.cleanup()
        # 记录命令
        self.add_cmd("git status")
        # 可以直接运行
        self.run_git()

    def git_reset(self):
        # 日常清理
        self.cleanup()
        # 文件选择
        files = self.get_file_list_by_status()
        self.create_check_box(files)
        # 记录命令
        self.add_cmd("git reset .")

    # - - - - - - - - -  - - - - - - - - -  - - - - - - - - -  - - - - - - - - -
    # 创建复选框
    def create_check_box(self, opt_list: list):
        # 创建
        self.check_box_select_all = QCheckBox('全选')
        # lambda可以传参:lambda: self.check_box_changes_all(<参数>)
        self.check_box_select_all.stateChanged.connect(lambda: self.check_box_changes_all())
        for ind in range(len(opt_list)):
            self.check_box.append(QCheckBox(opt_list[ind]))
            self.check_box[ind].stateChanged.connect(lambda: self.check_box_changes())
        # self.check_box_button = QPushButton('提交')
        # 布局
        ind = self.max_line_no
        self.gridLayout.addWidget(self.check_box_select_all, ind, 0)
        ind = ind + 1
        for check_box in self.check_box:
            self.gridLayout.addWidget(check_box, ind, 0)
            ind = ind + 1
        # self.gridLayout.addWidget(self.check_box_button, ind, 0)
        # 添加按钮回调
        # self.check_box_button.clicked.connect(self.check_box_ok)
        # 更新行号
        # self.max_line_no = ind + 1
        self.max_line_no = ind

    def check_box_ok(self):
        # 清除小部件
        if self.check_box:
            self.file_list.clear()
            for check_box in self.check_box:
                # 更新列表
                # if check_box.checkState() == Qt.Checked:
                #     self.file_list.append(check_box.text())
                # 清除
                check_box.deleteLater()
            self.check_box.clear()
        if self.check_box_select_all:
            self.check_box_select_all.deleteLater()
            self.check_box_select_all = None
        if self.check_box_button:
            self.check_box_button.deleteLater()
            self.check_box_button = None

    def check_box_changes_all(self):
        if self.check_box_select_all.checkState() == Qt.Checked:
            for check_box in self.check_box:
                check_box.setChecked(True)
        elif self.check_box_select_all.checkState() == Qt.Unchecked:
            for check_box in self.check_box:
                check_box.setChecked(False)

    def check_box_changes(self):
        all_checked = True
        # one_checked = False
        self.file_list.clear()
        for check_box in self.check_box:
            if not check_box.isChecked():
                all_checked = False  # 只要有一个没勾选
            else:
                self.file_list.append(check_box.text())  # 更新列表
            # else:
            #     one_checked = True  # 只要有一个勾选
        if all_checked:
            self.check_box_select_all.setCheckState(Qt.Checked)
        # elif one_checked:
            # self.check_box_select_all.setTristate()  # 设置为3态选择框
            # self.check_box_select_all.setCheckState(Qt.PartiallyChecked)
        # else:
        #   self.check_box_select_all.setTristate()  # 设置为3态选择框
        #   self.check_box_select_all.setCheckState(Qt.Unchecked)

        # 更新命令
        files = " ".join(self.file_list)
        self.update_cmd(files)

    # - - - - - - - - -  - - - - - - - - -  - - - - - - - - -  - - - - - - - - -
    # 清理临时的小部件
    def cleanup(self):
        self.check_box_ok()

    # - - - - - - - - -  - - - - - - - - -  - - - - - - - - -  - - - - - - - - -
    # 例子
    def addButtonExample(self):
        # 创建一些小部件放在顶级窗口中
        btn = QPushButton('press me')
        text = QLineEdit('enter text')
        listw = QListWidget()
        listw.addItems(["aa", "bb", "cc"])
        # self.gridLayout = QGridLayout(self)
        # 将部件添加到布局中的适当位置,
        # addWidget参数:Widget实例, 起始row, 起始column, 占多少行(高度),占多少列(宽度)
        self.gridLayout.addWidget(btn, 0, 0)
        self.gridLayout.addWidget(text, 1, 0)
        self.gridLayout.addWidget(listw, 2, 0)
        # self.setLayout(self.gridLayout)

    # 画图1
    def linePlot(self):
        plt1 = pg.PlotWidget()
        plt1.plot([i for i in range(10)], [i * i for i in range(10)])
        self.gridLayout.addWidget(plt1, 0, 1, 1, 1)

    # 画图2
    def scatterPlot(self):
        plt2 = pg.PlotWidget()
        x = np.random.normal(size=1000)
        y = np.random.normal(size=1000)
        plt2.plot(x, y, pen=None, symbol="o")
        self.gridLayout.addWidget(plt2, 1, 1, 1, 1)

    # 画图3
    def three_curves(self):
        plt3 = pg.PlotWidget(title="Three plot curves")
        x = np.arange(1000)
        y = np.random.normal(size=(3, 1000))
        for i in range(3):
            plt3.plot(x, y[i], pen=(i, 3))  # setting pen=(i,3) 自动创建3个不同颜色的笔
        self.gridLayout.addWidget(plt3, 2, 1, 1, 1)
class LevelSelector(QDialog):
    def __init__(self, parent):
        super(LevelSelector, self).__init__(parent)

        self.setWindowTitle("Level Selector")
        self.setModal(True)

        self.selected_world = 1
        self.selected_level = 1
        self.object_set = 0
        self.object_data_offset = 0x0
        self.enemy_data_offset = 0x0

        self.world_label = QLabel(parent=self, text="World")
        self.world_list = QListWidget(parent=self)
        self.world_list.addItems(WORLD_ITEMS)

        self.world_list.itemDoubleClicked.connect(self.on_ok)
        self.world_list.itemSelectionChanged.connect(self.on_world_click)

        self.level_label = QLabel(parent=self, text="Level")
        self.level_list = QListWidget(parent=self)

        self.level_list.itemDoubleClicked.connect(self.on_ok)
        self.level_list.itemSelectionChanged.connect(self.on_level_click)

        self.enemy_data_label = QLabel(parent=self, text="Enemy Data")
        self.enemy_data_spinner = Spinner(parent=self)

        self.object_data_label = QLabel(parent=self, text="Object Data")
        self.object_data_spinner = Spinner(self)

        self.object_set_label = QLabel(parent=self, text="Object Set")
        self.object_set_dropdown = QComboBox(self)
        self.object_set_dropdown.addItems(OBJECT_SET_ITEMS)

        self.button_ok = QPushButton("Ok", self)
        self.button_ok.clicked.connect(self.on_ok)
        self.button_cancel = QPushButton("Cancel", self)
        self.button_cancel.clicked.connect(self.close)

        self.window_layout = QGridLayout(self)

        self.window_layout.addWidget(self.world_label, 0, 0)
        self.window_layout.addWidget(self.level_label, 0, 1)

        self.window_layout.addWidget(self.world_list, 1, 0)
        self.window_layout.addWidget(self.level_list, 1, 1)

        self.window_layout.addWidget(self.enemy_data_label, 2, 0)
        self.window_layout.addWidget(self.object_data_label, 2, 1)
        self.window_layout.addWidget(self.enemy_data_spinner, 3, 0)
        self.window_layout.addWidget(self.object_data_spinner, 3, 1)

        self.window_layout.addWidget(self.object_set_label, 4, 0)
        self.window_layout.addWidget(self.object_set_dropdown, 4, 1)

        self.window_layout.addWidget(self.button_ok, 5, 0)
        self.window_layout.addWidget(self.button_cancel, 5, 1)

        self.setLayout(self.window_layout)

        self.world_list.setCurrentRow(1)  # select Level 1-1
        self.on_world_click()

    def keyPressEvent(self, key_event: QKeyEvent):
        if key_event.key() == Qt.Key_Escape:
            self.reject()

    def on_world_click(self):
        index = self.world_list.currentRow()

        assert index >= 0

        self.level_list.clear()

        # skip first meaningless item
        for level in Level.offsets[1:]:
            if level.game_world == index:
                if level.name:
                    self.level_list.addItem(level.name)

        if self.level_list.count():
            self.level_list.setCurrentRow(0)

            self.on_level_click()

    def on_level_click(self):
        index = self.level_list.currentRow()

        assert index >= 0

        self.selected_world = self.world_list.currentRow()
        self.selected_level = index + 1

        level_is_overworld = self.selected_world == OVERWORLD_MAPS_INDEX

        if level_is_overworld:
            level_array_offset = self.selected_level
        else:
            level_array_offset = Level.world_indexes[self.selected_world] + self.selected_level

        object_data_for_lvl = Level.offsets[level_array_offset].rom_level_offset

        if not level_is_overworld:
            object_data_for_lvl -= Level.HEADER_LENGTH

        self.object_data_spinner.setValue(object_data_for_lvl)

        if not level_is_overworld:
            enemy_data_for_lvl = Level.offsets[level_array_offset].enemy_offset
        else:
            enemy_data_for_lvl = 0

        if enemy_data_for_lvl > 0:
            # data in look up table is off by one, since workshop ignores the first byte
            enemy_data_for_lvl -= 1

        self.enemy_data_spinner.setValue(enemy_data_for_lvl)
        self.enemy_data_spinner.setEnabled(not level_is_overworld)

        # if self.selected_world >= WORLD_1_INDEX:
        object_set_index = Level.offsets[level_array_offset].real_obj_set
        self.object_set_dropdown.setCurrentIndex(object_set_index)

        self.button_ok.setDisabled(self.selected_world == 0)

    def on_ok(self, _):
        if self.selected_world == 0:
            return

        self.object_set = self.object_set_dropdown.currentIndex()
        self.object_data_offset = self.object_data_spinner.value()
        # skip the first byte, because it seems useless
        self.enemy_data_offset = self.enemy_data_spinner.value() + 1

        self.accept()

    def closeEvent(self, _close_event: QCloseEvent):
        self.reject()
class LevelSelector(QDialog):
    def __init__(self, parent):
        super(LevelSelector, self).__init__(parent)

        self.setWindowTitle("Level Selector")
        self.setModal(True)

        self.level_name = ""

        self.object_set = 0
        self.object_data_offset = 0x0
        self.enemy_data_offset = 0x0

        self.world_label = QLabel(parent=self, text="World")
        self.world_list = QListWidget(parent=self)
        self.world_list.addItems(WORLD_ITEMS)

        self.world_list.itemDoubleClicked.connect(self.on_ok)
        self.world_list.itemSelectionChanged.connect(self.on_world_click)

        self.level_label = QLabel(parent=self, text="Level")
        self.level_list = QListWidget(parent=self)

        self.level_list.itemDoubleClicked.connect(self.on_ok)
        self.level_list.itemSelectionChanged.connect(self.on_level_click)

        self.enemy_data_label = QLabel(parent=self, text="Enemy Data")
        self.enemy_data_spinner = Spinner(parent=self)

        self.object_data_label = QLabel(parent=self, text="Object Data")
        self.object_data_spinner = Spinner(self)

        self.object_set_label = QLabel(parent=self, text="Object Set")
        self.object_set_dropdown = QComboBox(self)
        self.object_set_dropdown.addItems(OBJECT_SET_ITEMS)

        self.button_ok = QPushButton("Ok", self)
        self.button_ok.clicked.connect(self.on_ok)
        self.button_cancel = QPushButton("Cancel", self)
        self.button_cancel.clicked.connect(self.close)

        stock_level_widget = QWidget()
        stock_level_layout = QGridLayout(stock_level_widget)

        stock_level_layout.addWidget(self.world_label, 0, 0)
        stock_level_layout.addWidget(self.level_label, 0, 1)

        stock_level_layout.addWidget(self.world_list, 1, 0)
        stock_level_layout.addWidget(self.level_list, 1, 1)

        self.source_selector = QTabWidget()
        self.source_selector.addTab(stock_level_widget, "Stock Levels")

        for world_number in range(WORLD_COUNT):
            world_number += 1

            world_map_select = WorldMapLevelSelect(world_number)
            world_map_select.level_selected.connect(
                self._on_level_selected_via_world_map)

            self.source_selector.addTab(world_map_select,
                                        f"World {world_number}")

        data_layout = QGridLayout()

        data_layout.addWidget(self.enemy_data_label, 0, 0)
        data_layout.addWidget(self.object_data_label, 0, 1)
        data_layout.addWidget(self.enemy_data_spinner, 1, 0)
        data_layout.addWidget(self.object_data_spinner, 1, 1)

        data_layout.addWidget(self.object_set_label, 2, 0)
        data_layout.addWidget(self.object_set_dropdown, 2, 1)

        data_layout.addWidget(self.button_ok, 3, 0)
        data_layout.addWidget(self.button_cancel, 3, 1)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.source_selector)
        main_layout.addLayout(data_layout)

        self.setLayout(main_layout)

        self.world_list.setCurrentRow(1)  # select Level 1-1
        self.on_world_click()

    def keyPressEvent(self, key_event: QKeyEvent):
        if key_event.key() == Qt.Key_Escape:
            self.reject()

    def on_world_click(self):
        index = self.world_list.currentRow()

        assert index >= 0

        self.level_list.clear()

        # skip first meaningless item
        for level in Level.offsets[1:]:
            if level.game_world == index:
                if level.name:
                    self.level_list.addItem(level.name)

        if self.level_list.count():
            self.level_list.setCurrentRow(0)

            self.on_level_click()

    def on_level_click(self):
        index = self.level_list.currentRow()

        assert index >= 0

        level_is_overworld = self.world_list.currentRow(
        ) == OVERWORLD_MAPS_INDEX

        if level_is_overworld:
            level_array_offset = index + 1
            self.level_name = ""
        else:
            level_array_offset = Level.world_indexes[
                self.world_list.currentRow()] + index + 1
            self.level_name = f"World {self.world_list.currentRow()}, "

        self.level_name += f"{Level.offsets[level_array_offset].name}"

        object_data_for_lvl = Level.offsets[
            level_array_offset].rom_level_offset

        if not level_is_overworld:
            object_data_for_lvl -= Level.HEADER_LENGTH

        if not level_is_overworld:
            enemy_data_for_lvl = Level.offsets[level_array_offset].enemy_offset
        else:
            enemy_data_for_lvl = 0

        if enemy_data_for_lvl > 0:
            # data in look up table is off by one, since workshop ignores the first byte
            enemy_data_for_lvl -= 1

        self.enemy_data_spinner.setEnabled(not level_is_overworld)

        # if self.world_list.currentRow() >= WORLD_1_INDEX:
        object_set_index = Level.offsets[level_array_offset].real_obj_set
        self.button_ok.setDisabled(level_is_overworld)

        self._fill_in_data(object_set_index, object_data_for_lvl,
                           enemy_data_for_lvl)

    def _fill_in_data(self, object_set: int, layout_address: int,
                      enemy_address: int):
        self.object_set_dropdown.setCurrentIndex(object_set)
        self.object_data_spinner.setValue(layout_address)
        self.enemy_data_spinner.setValue(enemy_address)

    def _on_level_selected_via_world_map(self, level_name: str,
                                         object_set: int, layout_address: int,
                                         enemy_address: int):
        self.level_name = level_name

        self._fill_in_data(object_set, layout_address, enemy_address)

        self.on_ok()

    def on_ok(self, _=None):
        self.object_set = self.object_set_dropdown.currentIndex()
        self.object_data_offset = self.object_data_spinner.value()
        # skip the first byte, because it seems useless
        self.enemy_data_offset = self.enemy_data_spinner.value() + 1

        self.accept()

    def closeEvent(self, _close_event: QCloseEvent):
        self.reject()
Exemple #26
0
from PySide2 import QtCore
from PySide2.QtWidgets import QApplication, QListWidget, QComboBox

if __name__ == '__main__':
    """
    Two views using each one a instance of our data
    When the data is change in QListWidget(), the data in QComboBox will not be changed
    """

    app = QApplication(sys.argv)

    # Let's make the QListWidget show this data
    data = ["ONE", "TWO", "THREE", "FOUR", "FIVE"]

    list_widget = QListWidget()
    list_widget.show()
    list_widget.addItems(data)

    # Let's make elements on QListWidget editable
    for index in range(list_widget.count()):
        item = list_widget.item(index)
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)

    # A comboBox showing the same data
    comboBox = QComboBox()
    comboBox.show()
    comboBox.addItems(data)

    sys.exit(app.exec_())
Exemple #27
0
class WindowWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Crawler UI")
        self.setFixedSize(300, 300)
        self.route_combo = QComboBox()
        self.button = QPushButton()
        self.title_label = QLabel()
        self.domain_input_field = QLineEdit()
        self.response_textbox = QTextEdit()
        self.response_window = QWidget()
        self.version_label = QLabel()
        self.warning_label = QLabel()
        self.message_box = QMessageBox()
        #block
        self.title_label2 = QLabel()
        self.language_label = QLabel()
        self.country_label = QLabel()
        self.email_label = QLabel()
        self.phone_label = QLabel()
        self.emails = QListWidget()
        self.phone_numbers = QListWidget()
        self.last_updated_label = QLabel()
        self.setup_ui()
        self.create_layout()
        self.check_requirements()

    def setup_ui(self):
        self.route_combo.addItem("Add route for crawling")
        self.route_combo.addItem("Get data from crawler")
        self.button.setText("Click Me")
        self.button.clicked.connect(self.route_choice)
        self.title_label.setText("CrawlerUI")
        self.title_label.setFont(QFont("Times New Roman", 15))
        self.title_label.setStyleSheet("color:black")
        self.title_label.setAlignment(Qt.AlignCenter)
        self.domain_input_field.setText("Example.com")
        self.version_label.setText("v1.0.0")
        self.version_label.setFont(QFont("Times New Roman", 12))
        self.version_label.setStyleSheet("color:black")
        self.version_label.setAlignment(Qt.AlignRight)
        self.warning_label.setFont(QFont("Times New Roman", 12))
        self.warning_label.setStyleSheet("color:black")
        self.warning_label.setAlignment(Qt.AlignCenter)
        self.response_window.setFixedSize(600, 600)

    def route_choice(self):
        if self.route_combo.currentText() == "Add route for crawling":
            self.create_messagebox()
        else:
            self.create_table()

    def create_messagebox(self):
        message_box = QMessageBox()
        message_box.setWindowTitle("Info")
        message = add_domain(self.domain_input_field.text())
        message_box.setStandardButtons(QMessageBox.Ok)
        if type(message) is str:
            message_box.setText(message)
            message_box.setIcon(QMessageBox.Icon.Critical)
        else:
            message_box.setIcon(QMessageBox.Icon.Information)
            if message["message"] == "added":
                message_box.setText(
                    f'Started crawling {self.domain_input_field.text()}')
            elif message["message"] == "started anew":
                message_box.setText(
                    f'Recrawling {self.domain_input_field.text()}')
            else:
                message_box.setText(
                    f'Already crawling {self.domain_input_field.text()}')
        message_box.exec_()

    def create_table(self):
        try:
            data = json.loads(get_domain_data(self.domain_input_field.text()))
            grid = QGridLayout()
            #title_label = QLabel()
            # title_label.repaint()
            # title_label.setText(f'Showing results for {data["domain"]}')
            # title_label.setFont(QFont("Times New Roman", 15))
            self.title_label2.repaint()
            self.title_label2.setText(f'Showing results for {data["domain"]}')
            self.title_label2.setFont(QFont("Times New Roman", 15))

            #language_label = QLabel()
            #language_label.setText(f'Language: {data["language"]}')
            self.language_label.repaint()
            self.language_label.setText(f'Language: {data["language"]}')
            email_label = QLabel()
            self.email_label.repaint()
            self.email_label.setText("Emails:")
            phone_label = QLabel()
            self.email_label.repaint()
            self.phone_label.setText("Phone numbers:")
            country_label = QLabel()
            self.country_label.repaint()
            self.country_label.setText(f'Country: {data["country"]}')
            phone_numbers = QListWidget()
            emails = QListWidget()
            self.phone_numbers.clear()
            self.emails.clear()
            self.phone_numbers.repaint()
            self.emails.repaint()
            if data["phone_numbers"]:
                self.phone_numbers.addItems(data["phone_numbers"])
            else:
                self.phone_numbers.addItems(["No phone numbers found"])
            if data["emails"]:
                self.emails.addItems(data["emails"])
            else:
                self.emails.addItems(["No emails found"])
            last_updated_label = QLabel()
            self.last_updated_label.repaint()
            self.last_updated_label.setText(
                f'Last updated at: {data["updated_at"]}')
            grid.addWidget(self.title_label2,
                           0,
                           0,
                           1,
                           2,
                           alignment=Qt.AlignCenter)
            grid.addWidget(self.language_label, 1, 0)
            grid.addWidget(self.country_label, 2, 0)
            grid.addWidget(self.email_label, 3, 0)
            grid.addWidget(self.phone_label, 3, 1)
            grid.addWidget(self.emails, 4, 0)
            grid.addWidget(self.phone_numbers, 4, 1)
            grid.addWidget(self.last_updated_label,
                           5,
                           0,
                           1,
                           2,
                           alignment=Qt.AlignRight)
            self.response_window.setLayout(grid)
            self.response_window.show()
            return
        except json.decoder.JSONDecodeError:
            data = get_domain_data(self.domain_input_field.text())
            message_box = QMessageBox()
            message_box.setText(data)
            message_box.setIcon(QMessageBox.Icon.Question)
            message_box.exec_()

    def create_layout(self):
        vbox = QVBoxLayout()
        vbox.addWidget(self.title_label)
        vbox.addWidget(self.route_combo)
        vbox.addWidget(self.domain_input_field)
        vbox.addWidget(self.button)
        vbox.addWidget(self.version_label)
        vbox.addWidget(self.warning_label)
        self.setLayout(vbox)

    def check_requirements(self):
        # display only most severe issue
        if not check_endpoint():
            self.warning_label.setText("Endpoint offline")
            self.button.setDisabled(True)
        elif not is_online():
            self.warning_label.setText("Check your internet connection!")
            self.button.setDisabled(True)
        else:
            self.warning_label.setText("Ready for crawling")
Exemple #28
0
class EMMAResolverPanel(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent=parent, f=Qt.Window)
        self.setWindowTitle(self.tr("EMMA Resolver"))
        self.SUPPORTED_DISTS = \
            [(DistributionType.Nonparametric, self.tr("Nonparametric")),
             (DistributionType.Normal, self.tr("Normal")),
             (DistributionType.Weibull, self.tr("Weibull")),
             (DistributionType.SkewNormal, self.tr("Skew Normal"))]

        self.init_ui()
        self.normal_msg = QMessageBox(self)
        self.__dataset = None  # type: GrainSizeDataset
        self.__result_list = []  # type: list[EMMAResult]
        self.neural_setting = NNResolverSettingWidget(parent=self)
        self.neural_setting.setting = NNResolverSetting(min_niter=800,
                                                        max_niter=1200,
                                                        tol=1e-6,
                                                        ftol=1e-8,
                                                        lr=5e-2)
        self.load_dialog = LoadDatasetDialog(parent=self)
        self.load_dialog.dataset_loaded.connect(self.on_dataset_loaded)
        self.file_dialog = QFileDialog(parent=self)
        self.emma_result_chart = EMMAResultChart(toolbar=True)
        self.emma_summary_chart = EMMASummaryChart(toolbar=True)

    def init_ui(self):
        self.setAttribute(Qt.WA_StyledBackground, True)
        self.main_layout = QGridLayout(self)
        # self.main_layout.setContentsMargins(0, 0, 0, 0)
        # control group
        self.control_group = QGroupBox(self.tr("Control"))
        self.control_layout = QGridLayout(self.control_group)
        self.main_layout.addWidget(self.control_group, 0, 0)
        self.load_dataset_button = QPushButton(qta.icon("fa.database"),
                                               self.tr("Load Dataset"))
        self.load_dataset_button.clicked.connect(self.on_load_clicked)
        self.control_layout.addWidget(self.load_dataset_button, 0, 0, 1, 2)
        self.configure_button = QPushButton(qta.icon("fa.gears"),
                                            self.tr("Configure Algorithm"))
        self.configure_button.clicked.connect(self.on_configure_clicked)
        self.control_layout.addWidget(self.configure_button, 1, 0, 1, 2)
        self.n_samples_label = QLabel(self.tr("N<sub>samples</sub>"))
        self.n_samples_display = QLabel(self.tr("Unknown"))
        self.control_layout.addWidget(self.n_samples_label, 2, 0)
        self.control_layout.addWidget(self.n_samples_display, 2, 1)
        self.distribution_label = QLabel(self.tr("Distribution Type"))
        self.distribution_combo_box = QComboBox()
        self.distribution_combo_box.addItems(
            [name for _, name in self.SUPPORTED_DISTS])
        self.control_layout.addWidget(self.distribution_label, 3, 0)
        self.control_layout.addWidget(self.distribution_combo_box, 3, 1)
        self.min_n_members_label = QLabel("Minimum N<sub>members</sub>")
        self.min_n_members_input = QSpinBox()
        self.min_n_members_input.setRange(1, 10)
        self.control_layout.addWidget(self.min_n_members_label, 4, 0)
        self.control_layout.addWidget(self.min_n_members_input, 4, 1)
        self.max_n_members_label = QLabel("Maximum N<sub>members</sub>")
        self.max_n_members_input = QSpinBox()
        self.max_n_members_input.setRange(1, 10)
        self.max_n_members_input.setValue(10)
        self.control_layout.addWidget(self.max_n_members_label, 5, 0)
        self.control_layout.addWidget(self.max_n_members_input, 5, 1)
        self.perform_button = QPushButton(qta.icon("fa.play-circle"),
                                          self.tr("Perform"))
        self.perform_button.clicked.connect(self.on_perform_clicked)
        self.perform_button.setEnabled(False)
        self.perform_with_customized_ems_button = QPushButton(
            qta.icon("fa.play-circle"), self.tr("Perform With Customized EMs"))
        self.perform_with_customized_ems_button.clicked.connect(
            self.on_perform_with_customized_ems)
        self.perform_with_customized_ems_button.setEnabled(False)
        self.control_layout.addWidget(self.perform_button, 6, 0, 1, 2)
        self.control_layout.addWidget(self.perform_with_customized_ems_button,
                                      7, 0, 1, 2)
        self.progress_bar = QProgressBar()
        self.progress_bar.setFormat(self.tr("EMMA Progress"))
        self.control_layout.addWidget(self.progress_bar, 8, 0, 1, 2)

        self.result_group = QGroupBox(self.tr("Result"))
        self.result_layout = QGridLayout(self.result_group)
        self.main_layout.addWidget(self.result_group, 0, 1)
        self.result_list_widget = QListWidget()
        self.result_layout.addWidget(self.result_list_widget, 0, 0, 1, 2)
        self.remove_result_button = QPushButton(qta.icon("mdi.delete"),
                                                self.tr("Remove"))
        self.remove_result_button.clicked.connect(self.on_remove_clicked)
        self.show_result_button = QPushButton(qta.icon("fa.area-chart"),
                                              self.tr("Show"))
        self.show_result_button.clicked.connect(self.on_show_clicked)
        self.load_dump_button = QPushButton(qta.icon("fa.database"),
                                            self.tr("Load Dump"))
        self.load_dump_button.clicked.connect(self.on_load_dump_clicked)
        self.save_button = QPushButton(qta.icon("fa.save"), self.tr("Save"))
        self.save_button.clicked.connect(self.on_save_clicked)
        self.remove_result_button.setEnabled(False)
        self.show_result_button.setEnabled(False)
        self.save_button.setEnabled(False)
        self.result_layout.addWidget(self.remove_result_button, 1, 0)
        self.result_layout.addWidget(self.show_result_button, 1, 1)
        self.result_layout.addWidget(self.load_dump_button, 2, 0)
        self.result_layout.addWidget(self.save_button, 2, 1)

    def show_message(self, title: str, message: str):
        self.normal_msg.setWindowTitle(title)
        self.normal_msg.setText(message)
        self.normal_msg.exec_()

    def show_info(self, message: str):
        self.show_message(self.tr("Info"), message)

    def show_warning(self, message: str):
        self.show_message(self.tr("Warning"), message)

    def show_error(self, message: str):
        self.show_message(self.tr("Error"), message)

    def on_dataset_loaded(self, dataset: GrainSizeDataset):
        self.__dataset = dataset
        self.n_samples_display.setText(str(self.__dataset.n_samples))
        self.perform_button.setEnabled(True)
        self.perform_with_customized_ems_button.setEnabled(True)

    def on_load_clicked(self):
        self.load_dialog.show()

    def on_configure_clicked(self):
        self.neural_setting.show()

    @property
    def distribution_type(self) -> DistributionType:
        distribution_type, _ = self.SUPPORTED_DISTS[
            self.distribution_combo_box.currentIndex()]
        return distribution_type

    @property
    def n_members_list(self):
        min_n = self.min_n_members_input.value()
        max_n = self.max_n_members_input.value()
        if min_n > max_n:
            min_n, max_n = max_n, min_n
        return list(range(min_n, max_n + 1, 1))

    @property
    def n_results(self) -> int:
        return len(self.__result_list)

    @property
    def selected_index(self):
        indexes = self.result_list_widget.selectedIndexes()
        if len(indexes) == 0:
            return 0
        else:
            return indexes[0].row()

    @property
    def selected_result(self):
        if self.n_results == 0:
            None
        else:
            return self.__result_list[self.selected_index]

    def on_perform_clicked(self):
        if self.__dataset is None:
            self.show_error(self.tr("Dataset has not been loaded."))
            return

        self.perform_button.setEnabled(False)
        self.perform_with_customized_ems_button.setEnabled(False)
        resolver = EMMAResolver()
        resolver_setting = self.neural_setting.setting
        results = []
        n_members_list = self.n_members_list
        self.progress_bar.setRange(0, len(n_members_list))
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat(self.tr("Performing EMMA [%v/%m]"))
        QCoreApplication.processEvents()
        for i, n_members in enumerate(n_members_list):
            result = resolver.try_fit(self.__dataset, self.distribution_type,
                                      n_members, resolver_setting)
            results.append(result)
            self.progress_bar.setValue(i + 1)
            QCoreApplication.processEvents()

        self.add_results(results)
        self.progress_bar.setFormat(self.tr("Finished"))
        self.perform_button.setEnabled(True)
        self.perform_with_customized_ems_button.setEnabled(True)
        if len(results) > 1:
            self.emma_summary_chart.show_distances(results)
            self.emma_summary_chart.show()

    def on_perform_with_customized_ems(self):
        if self.__dataset is None:
            self.show_error(self.tr("Dataset has not been loaded."))
            return

        filename, _ = self.file_dialog.getOpenFileName(
            self,
            self.
            tr("Choose a excel file which contains the customized EMs at the first sheet"
               ), None, f"{self.tr('Microsoft Excel')} (*.xlsx)")
        if filename is None or filename == "":
            return
        try:
            wb = openpyxl.load_workbook(filename,
                                        read_only=True,
                                        data_only=True)
            ws = wb[wb.sheetnames[0]]
            raw_data = [[value for value in row] for row in ws.values]
            classes_μm = np.array(raw_data[0][1:], dtype=np.float64)
            classes_φ = convert_μm_to_φ(classes_μm)
            em_distributions = [
                np.array(row[1:], dtype=np.float64) for row in raw_data[1:]
            ]
        except Exception as e:
            self.show_error(
                self.tr(
                    "Error raised while loading the customized EMs.\n    {0}").
                format(e.__str__()))
            return
        if len(classes_μm) < 10:
            self.show_error(
                self.tr("The length of grain-size classes is too less."))
            return
        for i in range(len(classes_μm) - 1):
            if classes_μm[i + 1] <= classes_μm[i]:
                self.show_error(
                    self.tr("The grain-size classes is not incremental."))
                return
        if np.any(np.isnan(classes_μm)):
            self.show_error(
                self.tr(
                    "There is at least one nan value in grain-size classes."))
            return
        if len(em_distributions) > 10:
            self.show_error(
                self.
                tr("There are more than 10 customized EMs in the first sheet, please check."
                   ))
            return
        for distribution in em_distributions:
            if len(classes_μm) != len(distribution):
                self.show_error(
                    self.
                    tr("Some distributions of customized EMs have different length with the grain-size classes."
                       ))
                return
            if np.any(np.isnan(distribution)):
                self.show_error(
                    self.
                    tr("There is at least one nan value in the frequceny distributions of EMs."
                       ))
                return
            if abs(np.sum(distribution) - 1.0) > 0.05:
                self.show_error(
                    self.
                    tr("The sum of some distributions of customized EMs are not equal to 1."
                       ))
                return

        self.perform_button.setEnabled(False)
        self.perform_with_customized_ems_button.setEnabled(False)
        resolver = EMMAResolver()
        resolver_setting = self.neural_setting.setting

        self.progress_bar.setRange(0, 1)
        self.progress_bar.setValue(0)
        self.progress_bar.setFormat(self.tr("Performing EMMA [%v/%m]"))
        QCoreApplication.processEvents()
        try:
            result = resolver.try_fit_with_fixed_ems(self.__dataset, classes_φ,
                                                     em_distributions,
                                                     resolver_setting)
            self.progress_bar.setValue(1)
            self.add_results([result])
            self.progress_bar.setFormat(self.tr("Finished"))
        except Exception as e:
            self.show_error(
                self.tr("Error raised while fitting.\n    {0}").format(
                    e.__str__()))
            self.progress_bar.setFormat(self.tr("Failed"))
        QCoreApplication.processEvents()
        self.perform_button.setEnabled(True)
        self.perform_with_customized_ems_button.setEnabled(True)

    def get_distribution_name(self, distribution_type: DistributionType):
        if distribution_type == DistributionType.Nonparametric:
            return self.tr("Nonparametric")
        elif distribution_type == DistributionType.Normal:
            return self.tr("Normal")
        elif distribution_type == DistributionType.Weibull:
            return self.tr("Weibull")
        elif distribution_type == DistributionType.SkewNormal:
            return self.tr("Skew Normal")
        elif distribution_type == DistributionType.Customized:
            return self.tr("Customized")
        else:
            raise NotImplementedError(distribution_type)

    def get_result_name(self, result: EMMAResult):
        return f"[{self.get_distribution_name(result.distribution_type)}]-[{result.n_members} EM(s)]"

    def add_results(self, results: typing.List[EMMAResult]):
        if self.n_results == 0:
            self.remove_result_button.setEnabled(True)
            self.show_result_button.setEnabled(True)
            self.save_button.setEnabled(True)

        self.__result_list.extend(results)
        self.result_list_widget.addItems(
            [self.get_result_name(result) for result in results])

    def on_remove_clicked(self):
        if self.n_results == 0:
            return
        else:
            index = self.selected_index
            self.__result_list.pop(index)
            self.result_list_widget.takeItem(index)

        if self.n_results == 0:
            self.remove_result_button.setEnabled(False)
            self.show_result_button.setEnabled(False)
            self.save_button.setEnabled(False)

    def on_show_clicked(self):
        result = self.selected_result
        if result is None:
            return
        else:
            self.emma_result_chart.show_result(result)
            self.emma_result_chart.show()

    def on_load_dump_clicked(self):
        filename, _ = self.file_dialog.getOpenFileName(
            self, self.tr("Select the dump file of the EMMA result(s)"), None,
            f"{self.tr('Binary Dump')} (*.dump)")
        if filename is None or filename == "":
            return
        with open(filename, "rb") as f:
            results = pickle.load(f)
            invalid = False
            if isinstance(results, list):
                for result in results:
                    if not isinstance(result, EMMAResult):
                        invalid = True
                        break
            else:
                invalid = True
            if invalid:
                self.show_error(
                    self.tr("The dump file does not contain any EMMA result."))
                return
            else:
                self.add_results(results)

    def save_result_excel(self, filename: str, result: EMMAResult):
        # get the mode size of each end-members
        modes = [(i, result.dataset.classes_μm[np.unravel_index(
            np.argmax(result.end_members[i]), result.end_members[i].shape)])
                 for i in range(result.n_members)]
        # sort them by mode size
        modes.sort(key=lambda x: x[1])
        wb = openpyxl.Workbook()
        prepare_styles(wb)

        ws = wb.active
        ws.title = self.tr("README")
        description = \
            """
            This Excel file was generated by QGrain ({0}).

            Please cite:
            Liu, Y., Liu, X., Sun, Y., 2021. QGrain: An open-source and easy-to-use software for the comprehensive analysis of grain size distributions. Sedimentary Geology 423, 105980. https://doi.org/10.1016/j.sedgeo.2021.105980

            It contanins three sheets:
            1. The first sheet is the dataset which was used to perform the EMMA algorithm.
            2. The second sheet is used to put the distributions of all end-members.
            3. The third sheet is the end-member fractions of all samples.

            This EMMA algorithm was implemented by QGrian, using the famous machine learning framework, PyTorch.

            EMMA algorithm details
                N_samples: {1},
                Distribution Type: {2},
                N_members: {3},
                N_iterations: {4},
                Spent Time: {5} s,

                Computing Device: {6},
                Distance: {7},
                Minimum N_iterations: {8},
                Maximum N_iterations: {9},
                Learning Rate: {10},
                eps: {11},
                tol: {12},
                ftol: {13}

            """.format(QGRAIN_VERSION,
                    result.dataset.n_samples,
                    result.distribution_type.name,
                    result.n_members,
                    result.n_iterations,
                    result.time_spent,
                    result.resolver_setting.device,
                    result.resolver_setting.distance,
                    result.resolver_setting.min_niter,
                    result.resolver_setting.max_niter,
                    result.resolver_setting.lr,
                    result.resolver_setting.eps,
                    result.resolver_setting.tol,
                    result.resolver_setting.ftol)

        def write(row, col, value, style="normal_light"):
            cell = ws.cell(row + 1, col + 1, value=value)
            cell.style = style

        lines_of_desc = description.split("\n")
        for row, line in enumerate(lines_of_desc):
            write(row, 0, line, style="description")
        ws.column_dimensions[column_to_char(0)].width = 200

        ws = wb.create_sheet(self.tr("Dataset"))
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for col, value in enumerate(result.dataset.classes_μm, 1):
            write(0, col, value, style="header")
            ws.column_dimensions[column_to_char(col)].width = 10
        for row, sample in enumerate(result.dataset.samples, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, sample.name, style=style)
            for col, value in enumerate(sample.distribution, 1):
                write(row, col, value, style=style)
            QCoreApplication.processEvents()

        ws = wb.create_sheet(self.tr("End-members"))
        write(0, 0, self.tr("End-member"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for col, value in enumerate(result.dataset.classes_μm, 1):
            write(0, col, value, style="header")
            ws.column_dimensions[column_to_char(col)].width = 10
        for row, (index, _) in enumerate(modes, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, f"EM{row}", style=style)
            for col, value in enumerate(result.end_members[index], 1):
                write(row, col, value, style=style)
            QCoreApplication.processEvents()

        ws = wb.create_sheet(self.tr("Fractions"))
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for i in range(result.n_members):
            write(0, i + 1, f"EM{i+1}", style="header")
            ws.column_dimensions[column_to_char(i + 1)].width = 10
        for row, fractions in enumerate(result.fractions, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, result.dataset.samples[row - 1].name, style=style)
            for col, (index, _) in enumerate(modes, 1):
                write(row, col, fractions[index], style=style)
            QCoreApplication.processEvents()

        wb.save(filename)
        wb.close()

    def on_save_clicked(self):
        if self.n_results == 0:
            self.show_warning(
                self.tr("There is not an EMMA result in the list."))
            return

        filename, _ = self.file_dialog.getSaveFileName(
            self,
            self.tr("Choose a filename to save the EMMA result(s) in list"),
            None,
            f"{self.tr('Binary Dump')} (*.dump);;{self.tr('Microsoft Excel')} (*.xlsx)"
        )
        if filename is None or filename == "":
            return
        _, ext = os.path.splitext(filename)

        if ext == ".dump":
            with open(filename, "wb") as f:
                pickle.dump(self.__result_list, f)
                self.show_info(self.tr("All results in list has been saved."))
        elif ext == ".xlsx":
            try:
                result = self.selected_result
                self.save_result_excel(filename, result)
                self.show_info(self.tr("The selected result has been saved."))
            except Exception as e:
                self.show_error(
                    self.tr(
                        "Error raised while saving it to Excel file.\n    {0}"
                    ).format(e.__str__()))
                return
class FilterDock(QDockWidget):

    def __init__(self, platforms, regions, genres, years):
        super(FilterDock, self).__init__()

        # QDockWidget settings
        self.setAllowedAreas(Qt.BottomDockWidgetArea)
        self.setFeatures(QDockWidget.DockWidgetClosable)
        self.setFixedHeight(150)
        self.setVisible(False)
        self.setWindowTitle("Filter options")

        # The selected items for each widget are saved in a set-dictionary
        self._selections = defaultdict(set)

        #  Widget settings
        # Platform widgets
        self._platformLabel = QLabel("Platform")
        self._platforms = QListWidget()
        self._platforms.addItems(platforms)
        self._platforms.setSelectionMode(QAbstractItemView.MultiSelection)
        self._platforms.setMaximumWidth(200)
        self._platformVBox = QVBoxLayout()
        self._platformVBox.addWidget(self._platformLabel, 0)
        self._platformVBox.addWidget(self._platforms, 1)

        # Region widgets
        self._regionLabel = QLabel("Region")
        self._regions = QListWidget()
        self._regions.addItems(regions)
        self._regions.setSelectionMode(QAbstractItemView.MultiSelection)
        self._regions.setMaximumWidth(200)
        self._regionVBox = QVBoxLayout()
        self._regionVBox.addWidget(self._regionLabel, 0)
        self._regionVBox.addWidget(self._regions, 1)

        # Genre widgets
        self._genreLabel = QLabel("Genre")
        self._genres = QListWidget()
        self._genres.addItems(genres)
        self._genres.setSelectionMode(QAbstractItemView.MultiSelection)
        self._genres.setMaximumWidth(300)
        self._genreVBox = QVBoxLayout()
        self._genreVBox.addWidget(self._genreLabel, 0)
        self._genreVBox.addWidget(self._genres, 1)

        # Year widgets
        self._yearLabel = QLabel("Year")
        self._years = QListWidget()
        self._years.addItems(years)
        self._years.setSelectionMode(QAbstractItemView.MultiSelection)
        self._years.setMaximumWidth(75)
        self._yearsVbox = QVBoxLayout()
        self._yearsVbox.addWidget(self._yearLabel, 0)
        self._yearsVbox.addWidget(self._years, 1)

        # Inventory widgets
        self._itemType = {1: "Game", 2: "Console", 3: "Accessory"}
        self._item = QCheckBox(self._itemType[1])
        self._item.setTristate(True)
        self._item.setCheckState(Qt.PartiallyChecked)
        self._manual = QCheckBox("Manual")
        self._manual.setTristate(True)
        self._manual.setCheckState(Qt.PartiallyChecked)
        self._box = QCheckBox("Box")
        self._box.setTristate(True)
        self._box.setCheckState(Qt.PartiallyChecked)
        self._inventoryLabelsVBox = QVBoxLayout()
        self._inventorySelectionsVBox = QVBoxLayout()
        self._inventorySelectionsVBox.addStretch(3)
        self._inventorySelectionsVBox.addWidget(self._item, 0)
        self._inventorySelectionsVBox.addWidget(self._box, 1)
        self._inventorySelectionsVBox.addWidget(self._manual, 2)
        self._inventorySelectionsVBox.setAlignment(Qt.AlignLeft)
        self._haveHBox = QHBoxLayout()
        self._haveHBox.addLayout(self._inventoryLabelsVBox, 0)
        self._haveHBox.addLayout(self._inventorySelectionsVBox, 1)
        self._inventoryGroup = QGroupBox("Inventory")
        self._inventoryGroup.setMaximumWidth(120)
        self._inventoryGroup.setLayout(self._haveHBox)

        # Clear and Apply button widgets
        self._clearBtn = QPushButton("Clear selection")
        self._clearBtn.setMaximumSize(self._clearBtn.sizeHint())
        self._clearBtn.clicked.connect(self._clearFilters)
        self._btnHBox = QHBoxLayout()
        self._btnHBox.setAlignment(Qt.AlignBottom | Qt.AlignRight)
        self._btnHBox.addWidget(self._clearBtn, 0)

        # General layout
        mainHBox = QHBoxLayout()
        mainHBox.setAlignment(Qt.AlignLeft)
        mainHBox.addLayout(self._platformVBox, 0)
        mainHBox.addLayout(self._regionVBox, 0)
        mainHBox.addLayout(self._genreVBox, 0)
        mainHBox.addLayout(self._yearsVbox, 0)
        mainHBox.addWidget(self._inventoryGroup, 0)
        mainHBox.addLayout(self._btnHBox, 0)
        mainWidget = QWidget()
        mainWidget.setLayout(mainHBox)
        self.setWidget(mainWidget)

    def _clearFilters(self):
        self._platforms.clearSelection()
        self._regions.clearSelection()
        self._genres.clearSelection()
        self._years.clearSelection()
        self._item.setCheckState(Qt.PartiallyChecked)
        self._box.setCheckState(Qt.PartiallyChecked)
        self._manual.setCheckState(Qt.PartiallyChecked)
        logger.info("Cleared all filters.")

    def getSelections(self):
        self._selections = defaultdict(set)  # Reset selections
        if len(self._platforms.selectedItems()) > 0:
            platforms = [x.text() for x in self._platforms.selectedItems()]
            for platform in platforms:
                self._selections["Platform"].add(platform)
        if len(self._regions.selectedItems()) > 0:
            regions = [x.text() for x in self._regions.selectedItems()]
            for region in regions:
                self._selections["Region"].add(region)
        if len(self._genres.selectedItems()) > 0:
            genres = [x.text() for x in self._genres.selectedItems()]
            for genre in genres:
                self._selections["Genre"].add(genre)
        if len(self._years.selectedItems()) > 0:
            years = [x.text() for x in self._years.selectedItems()]
            for year in years:
                self._selections["Year"].add(year)
        if self._item.checkState() is not Qt.PartiallyChecked:
            self._selections[self._item.text()].add("Yes" if self._item.isChecked() else "No")
        if self._manual.checkState() is not Qt.PartiallyChecked:
            self._selections["Manual"].add("Yes" if self._manual.isChecked() else "No")
        if self._box.checkState() is not Qt.PartiallyChecked:
            self._selections["Box"].add("Yes" if self._box.isChecked() else "No")

        return self._selections

    def setItemType(self, itemType: int):
        if 0 < itemType < 4:
            if self._item.text() in self._selections:
                # Delete previous item entry so we don't search for the wrong type in the wrong table
                del self._selections[self._item.text()]
            self._item.setText(self._itemType[itemType])

    def toggleVisibility(self):
        self.setVisible(False if self.isVisible() else True)

    def updatePlatforms(self, platforms):
        self._platforms.clear()
        self._platforms.addItems(platforms)
        logger.info("Updated platforms list.")

    def updateRegions(self, regions):
        self._regions.clear()
        self._regions.addItems(regions)
        logger.info("Updated regions list.")

    def updateGenres(self, genres):
        self._genres.clear()
        self._genres.addItems(genres)
        logger.info("Updated genres list.")

    def updateYears(self, years):
        self._years.clear()
        self._years.addItems(years)
        logger.info("Updated years list.")