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
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)
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)
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)
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)
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]
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)
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
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)
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)
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) ])
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()
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.")
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
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()
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()
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)
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()
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_())
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")
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.")