def populate_combobox(self): self.connect_database() model = QSqlTableModel() model.setTable('products') column = model.fieldIndex('name') model.select() self.items_combobox.setModel(model) self.items_combobox.setModelColumn(column)
class FilteringComboBox(QComboBox): """Combination of QCombobox and QLineEdit with autocompletionself. Line edit and completer model is taken from QSqlTable mod Args: table (str): db table name containing data for combobox column (str): column name containing data for combobox """ def __init__(self, table, column, placeholderText, parent=None): super(FilteringComboBox, self).__init__(parent) self.parent = parent self.setEditable(True) self.setFocusPolicy(Qt.StrongFocus) self.setInsertPolicy(QComboBox.NoInsert) self.lineEdit().setPlaceholderText(placeholderText) # setup data model self._model = QSqlTableModel(self) self._model.setTable(table) self._model.select() col_num = self._model.fieldIndex(column) self._model.sort(col_num, Qt.AscendingOrder) self.setModel(self._model) self.setModelColumn(col_num) # setup completer self._proxy = QSortFilterProxyModel(self) self._proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self._proxy.setSourceModel(self._model) self._proxy.setFilterKeyColumn(col_num) self._completer = QCompleter(self) self._completer.setModel(self._proxy) self._completer.setCompletionColumn(col_num) self._completer.activated.connect(self.onCompleterActivated) self._completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) self.setCompleter(self._completer) self.lineEdit().textEdited.connect(self._proxy.setFilterFixedString) @pyqtSlot(str) def onCompleterActivated(self, text): if not text: return self.setCurrentIndex(self.findText(text)) self.activated[str].emit(self.currentText()) def updateModel(self): self._model.select()
class PropertiesService(QObject): controller = None # type: PropertiesController question_type_model = None # type: QSqlTableModel question_type_filter_proxy_model = None # type: 'QuestionTypeFilterProxyModel' def __init__(self, parent=None): QObject.__init__(self, parent) self.connect_slots() def connect_slots(self): application.app.aboutToQuit.connect(self.on_about_to_quit) def dispose(self): pass def show(self, tab_wanted: str = 'general'): from genial.services import document_service if document_service.database is not None: if self.question_type_model is None: self.question_type_model = QSqlTableModel( self, document_service.database) self.question_type_model.setTable("question_type") self.question_type_model.setEditStrategy( QSqlTableModel.OnManualSubmit) self.question_type_filter_proxy_model = QuestionTypeFilterProxyModel( ) self.question_type_filter_proxy_model.setSourceModel( self.question_type_model) self.question_type_filter_proxy_model.sort( self.question_type_model.fieldIndex("position"), Qt.AscendingOrder) self.question_type_filter_proxy_model.setDynamicSortFilter( True) if self.controller is None: self.controller = PropertiesController() self.controller.start() self.controller.show(tab_wanted) @pyqtSlot() def on_about_to_quit(self): self.dispose()
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.table = QTableView() self.model = QSqlTableModel(db=db) self.table.setModel(self.model) # tag::sortTable[] self.model.setTable("Track") # idx = self.model.fieldIndex("Milliseconds") # self.model.setSort(idx, Qt.DescendingOrder) # self.model.setHeaderData(1, Qt.Horizontal, "Name") # self.model.setHeaderData(2, Qt.Horizontal, "Album (ID)") # self.model.setHeaderData(3, Qt.Horizontal, "Media Type (ID)") # self.model.setHeaderData(4, Qt.Horizontal, "Genre (ID)") # self.model.setHeaderData(5, Qt.Horizontal, "Composer") column_titles = { "Name": "Name", "AlbumId": "Album (ID)", "MediaTypeId": "Media Type (ID)", "GenreId": "Genre (ID)", "Composer": "Composer", } for n, t in column_titles.items(): idx = self.model.fieldIndex(n) self.model.setHeaderData(idx, Qt.Horizontal, t) self.model.select() # end::sortTable[] # self.model.setTable("Track") # self.model.select() self.setMinimumSize(QSize(1024, 600)) self.setCentralWidget(self.table)
class Filtrage_peche_dialog(QDialog, Ui_dlgPecheRechercheForm): ''' Class de la fenêtre permettant le filtrage attributaire des inventaires de reproduction :param QDialog: Permet d'afficher l'interface graphique comme une fenêtre indépendante :type QDialog: QDialog :param Ui_dlgPecheRechercheForm: Class du script de l'interface graphique du formulaire, apporte les éléments de l'interface :type Ui_dlgPecheRechercheForm: class ''' def __init__(self, db, dbType, dbSchema, modelPeche, parent=None): ''' Constructeur, récupération de variable, connection des événements et remplissage des combobox :param db: définie dans le setupModel(), représente la connexion avec la base de données :type db: QSqlDatabase :param dbType: type de la base de données (postgre) :type dbType: str :param dbSchema: nom du schéma sous PostgreSQL contenant les données (data) :type dbSchema: unicode :param modelPeche: modèle pêche qui contient les données de la base de données :type modelPeche: QSqlRelationalTableModel :param parent: défini que cette fenêtre n'hérite pas d'autres widgets :type parent: NoneType ''' super(Filtrage_peche_dialog, self).__init__(parent) self.db = db self.dbType = dbType self.dbSchema = dbSchema self.modelPeche = modelPeche self.setupUi(self) self.btnAnnuler.clicked.connect(self.reject) self.btnExec.clicked.connect(self.execution) self.btnRaz.clicked.connect(self.raz) self.btnEt.clicked.connect(self.et) self.btnOu.clicked.connect(self.ou) self.btnPrevisualiser.clicked.connect(self.previSql) self.btnCode.clicked.connect(self.ajoutCode) self.btnId.clicked.connect(self.ajoutId) self.btnPdpg.clicked.connect(self.ajoutPdpg) self.btnDate.clicked.connect(self.ajoutDate) self.btnRiviere.clicked.connect(self.ajoutRiviere) self.btnAappma.clicked.connect(self.ajoutAappma) self.btnMeau.clicked.connect(self.ajoutMeau) self.btnMotif.clicked.connect(self.ajoutMotif) self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.aappmaBool = False self.motifBool = False self.pdpgBool = False self.ceauBool = False self.meauBool = False self.anneeBool = False self.wwhere = "" self.modelPdpg = QSqlTableModel(self, self.db) wrelation = "contexte_pdpg" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelPdpg.setTable(wrelation) self.modelPdpg.setSort(2, Qt.AscendingOrder) if (not self.modelPdpg.select()): QMessageBox.critical(self, u"Remplissage du modèle PDPG", self.modelPdpg.lastError().text(), QMessageBox.Ok) self.cmbPdpg.setModel(self.modelPdpg) self.cmbPdpg.setModelColumn(self.modelPdpg.fieldIndex("pdpg_nom")) self.modelAappma = QSqlTableModel(self, self.db) wrelation = "aappma" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelAappma.setTable(wrelation) self.modelAappma.setSort(1, Qt.AscendingOrder) if (not self.modelAappma.select()): QMessageBox.critical(self, u"Remplissage du modèle AAPPMA", self.modelAappma.lastError().text(), QMessageBox.Ok) self.cmbAappma.setModel(self.modelAappma) self.cmbAappma.setModelColumn(self.modelAappma.fieldIndex("apma_nom")) self.modelRiviere = QSqlTableModel(self, self.db) wrelation = "cours_eau" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelRiviere.setTable(wrelation) self.modelRiviere.setFilter("ceau_nom <> 'NR'") self.modelRiviere.setSort(2, Qt.AscendingOrder) if (not self.modelRiviere.select()): QMessageBox.critical(self, u"Remplissage du modèle Rivière", self.modelRiviere.lastError().text(), QMessageBox.Ok) self.cmbRiviere.setModel(self.modelRiviere) self.cmbRiviere.setModelColumn( self.modelRiviere.fieldIndex("ceau_nom")) self.modelMeau = QSqlQueryModel(self) wrelation = "masse_eau" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelMeau.setQuery( "select meau_code, meau_code || ' ; ' || meau_nom from " + wrelation + " order by meau_code;", self.db) if self.modelMeau.lastError().isValid(): QMessageBox.critical(self, u"Remplissage du modèle Masse d'eau", self.modelMeau.lastError().text(), QMessageBox.Ok) self.cmbMeau.setModel(self.modelMeau) self.cmbMeau.setModelColumn(1) self.ModelMotif = QSqlTableModel(self, self.db) wrelation = "motif_peche" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.ModelMotif.setTable(wrelation) self.ModelMotif.setSort(1, Qt.AscendingOrder) if (not self.ModelMotif.select()): QMessageBox.critical(self, u"Remplissage du modèle Motif", self.ModelMotif.lastError().text(), QMessageBox.Ok) self.cmbMotif.setModel(self.ModelMotif) self.cmbMotif.setModelColumn(self.ModelMotif.fieldIndex("mope_motif")) def reject(self): '''Ferme la fenêtre si clic sur le bouton annuler''' QDialog.reject(self) def raz(self): '''Réinitialise toutes les variables de la fenêtre afin de recommencer une nouvelle requête''' self.spnId.setValue(0) self.wrq = "" self.txtSql.setText("") self.wwhere = "" self.datePeche.setDate(QDate(2000, 1, 1)) self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnCode.setEnabled(True) self.btnId.setEnabled(True) self.btnPdpg.setEnabled(True) self.btnDate.setEnabled(True) self.btnRiviere.setEnabled(True) self.btnAappma.setEnabled(True) self.btnMeau.setEnabled(True) self.btnMotif.setEnabled(True) self.aappmaBool = False self.motifBool = False self.pdpgBool = False self.ceauBool = False self.meauBool = False self.anneeBool = False def et(self): '''Change l'état des boutons et ajoute "and" à la requête''' self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnCode.setEnabled(True) self.btnId.setEnabled(True) if self.aappmaBool == False: self.btnAappma.setEnabled(True) if self.motifBool == False: self.btnMotif.setEnabled(True) if self.pdpgBool == False: self.btnPdpg.setEnabled(True) if self.ceauBool == False: self.btnRiviere.setEnabled(True) if self.meauBool == False: self.btnMeau.setEnabled(True) if self.anneeBool == False: self.btnDate.setEnabled(True) self.wwhere += " AND " def ou(self): '''Change l'état des boutons et ajoute "or" à la requête''' self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnCode.setEnabled(True) self.btnId.setEnabled(True) self.btnPdpg.setEnabled(True) self.btnDate.setEnabled(True) self.btnRiviere.setEnabled(True) self.btnAappma.setEnabled(True) self.btnMeau.setEnabled(True) self.btnMotif.setEnabled(True) self.aappmaBool = False self.motifBool = False self.pdpgBool = False self.ceauBool = False self.meauBool = False self.anneeBool = False self.wwhere += " OR " def ajoutCode(self): '''Change l'état des boutons et ajoute un critère de code opération à la requête''' self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.wcode = self.leCodeOpe.text() if self.leCodeOpe.text() != "": if self.wcode != "": self.wwhere += "opep_ope_code ilike '%" + self.wcode + "%'" self.leCodeOpe.setText("") self.leCodeOpe.setFocus() def ajoutId(self): '''Change l'état des boutons et ajoute un critère d'id à la requête''' self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.wid = self.spnId.value() if self.spnId.value() != "": if self.wid != "": self.wwhere += "opep_id = '" + str(self.wid) + "'" self.spnId.setValue(0) self.spnId.setFocus() def ajoutDate(self): '''Change l'état des boutons et ajoute un critère de date à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.anneeBool = True self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.wopep_date = self.datePeche.date().toString("yyyy") if self.wopep_date != "": self.wwhere += "date_part('year', opep_date) = '" + self.wopep_date + "'" def ajoutPdpg(self): '''Change l'état des boutons et ajoute un critère de pdpg à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.pdpgBool = True wfromOperation = "operation" wfromStation = "station" wfromPeche = "ope_peche_elec" if self.dbType == "postgres": self.wfromPdpg = self.dbSchema + "." + wfromPeche + ", " + self.dbSchema + "." + wfromOperation + ", " + self.dbSchema + "." + wfromStation wrecord = self.cmbPdpg.model().record(self.cmbPdpg.currentIndex()) self.wsta_pdpg = wrecord.value(0) if self.cmbPdpg.currentText() != "": if self.wsta_pdpg != "": self.wwhere += " opep_id in (select distinct opep_id from " + self.wfromPdpg + " where (opep_ope_code = ope_code) and (ope_sta_id = sta_id) and sta_pdpg_id = '" + str( self.wsta_pdpg) + "')" def ajoutRiviere(self): '''Change l'état des boutons et ajoute un critère de cours d'eau à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.ceauBool = True wfromOperation = "operation" wfromStation = "station" wfromPeche = "ope_peche_elec" if self.dbType == "postgres": self.wfromCeau = self.dbSchema + "." + wfromPeche + ", " + self.dbSchema + "." + wfromOperation + ", " + self.dbSchema + "." + wfromStation wrecord = self.cmbRiviere.model().record( self.cmbRiviere.currentIndex()) self.wsta_riviere = wrecord.value(0) if self.cmbRiviere.currentText() != "": if self.wsta_riviere != "": self.wwhere += " opep_id in (select distinct opep_id from " + self.wfromCeau + " where (opep_ope_code = ope_code) and (ope_sta_id = sta_id) and sta_ceau_id = '" + str( self.wsta_riviere) + "')" def ajoutAappma(self): '''Change l'état des boutons et ajoute un critère d'AAPPMA à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.aappmaBool = True wfromOperation = "operation" wfromStation = "station" wfromPeche = "ope_peche_elec" if self.dbType == "postgres": self.wfromAappma = self.dbSchema + "." + wfromPeche + ", " + self.dbSchema + "." + wfromOperation + ", " + self.dbSchema + "." + wfromStation wrecord = self.cmbAappma.model().record(self.cmbAappma.currentIndex()) self.wsta_aappma = wrecord.value(0) if self.cmbAappma.currentText() != "": if self.wsta_aappma != "": self.wwhere += " opep_id in (select distinct opep_id from " + self.wfromAappma + " where (opep_ope_code = ope_code) and (ope_sta_id = sta_id) and sta_apma_id = '" + str( self.wsta_aappma) + "')" def ajoutMeau(self): '''Change l'état des boutons et ajoute un critère de Masse d'eau à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.meauBool = True wfromOperation = "operation" wfromStation = "station" wfromPeche = "ope_peche_elec" if self.dbType == "postgres": self.wfromMeau = self.dbSchema + "." + wfromPeche + ", " + self.dbSchema + "." + wfromOperation + ", " + self.dbSchema + "." + wfromStation wrecord = self.cmbMeau.model().record(self.cmbMeau.currentIndex()) self.wsta_meau = wrecord.value(0) if self.cmbMeau.currentText() != "": if self.wsta_meau != "": self.wwhere += " opep_id in (select distinct opep_id from " + self.wfromMeau + " where (opep_ope_code = ope_code) and (ope_sta_id = sta_id) and sta_meau_code = '" + str( self.wsta_meau) + "')" def ajoutMotif(self): '''Change l'état des boutons et ajoute un critère de motif de pêche à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnCode.setEnabled(False) self.btnId.setEnabled(False) self.btnPdpg.setEnabled(False) self.btnDate.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnMeau.setEnabled(False) self.btnMotif.setEnabled(False) self.motifBool = True wrecord = self.cmbMotif.model().record(self.cmbMotif.currentIndex()) self.wopep_motif = wrecord.value(0) if self.cmbMotif.currentText() != "": if self.wopep_motif != "": self.wwhere += "opep_mope_id = '" + str(self.wopep_motif) + "'" def creaRequete(self): # def previSql(self): '''Regroupe les différentes variables contenant les clauses de la requête SQL et les concatène pour en faire une requête exécutable''' self.wrq = "" # Construit la clause FROM de la requête cfrom = "ope_peche_elec" if self.dbType == "postgres": cfrom = self.dbSchema + "." + cfrom # Construit la clause SELECT et ajoute la clause FROM à la requête self.wrq = "SELECT DISTINCT opep_id FROM " + cfrom # Construit la clause WHERE et ORDER BY et l'ajoute à la requête if self.wwhere != "": #Supprime l'opérateur "and" ou "or" si celui-ci n'est pas suivi d'un critère operateurs = ["AND", "OR"] fin_where = self.wwhere[-5:] for ext in operateurs: if ext in fin_where: self.wwhere = self.wwhere[:-4] self.wrq += " WHERE " + self.wwhere + " ORDER BY opep_id" else: self.wrq += " ORDER BY opep_id" def previSql(self): '''Permet de prévisualiser la requête avant de l'éxecuter''' self.txtSql.setText("") self.creaRequete() # Affiche la requête self.txtSql.setText(self.wrq) def execution(self): '''Permet d'éxecuter la requête''' # Vérifie la non présence de mot pouvant endommager la base de données erreur = False interdit = [ "update", "delete", "insert", "intersect", "duplicate", "merge", "truncate", "create", "drop", "alter" ] if self.txtSql.toPlainText() != "": self.requete = self.txtSql.toPlainText() else: self.creaRequete() self.requete = self.wrq testRequete = self.requete.lower() for mot in interdit: if mot in testRequete: erreur = True if erreur == True: QMessageBox.critical( self, u"Erreur SQL", u"Vous essayez d'exécuter une requête qui peut endommager la base de données !", QMessageBox.Ok) # Après récupération du contenu de la zone de texte, exécute la requête else: query = QSqlQuery(self.db) query.prepare(self.requete) if query.exec_(): wparam = "" while query.next(): wparam += str(query.value(0)) + "," if (wparam != ""): wparam = "(" + wparam[0:len(wparam) - 1] + ")" if self.modelPeche: # Filtre le modèle des inventaires de reproduction et ferme la fenêtre self.modelPeche.setFilter("opep_id in %s" % wparam) self.modelPeche.select() QDialog.accept(self) else: QMessageBox.information( self, "Filtrage", u"Aucun pêche électrique ne correspond aux critères ...", QMessageBox.Ok) else: QMessageBox.critical(self, u"Erreur SQL", query.lastError().text(), QMessageBox.Ok)
class OrganTab(QWidget): def __init__(self, ctx, *args, **kwargs): super(OrganTab, self).__init__(*args, **kwargs) self.ctx = ctx self.alfas = None self.betas = None self.organ_dose_db = None self.organ_names = [] self.fig = {} self.is_quick_mode = False self.show_hk = False self.show_dosemap = False self.show_distmap = False self.show_dosedist = False self.initModel() self.initVar() self.initUI() self.sigConnect() def initVar(self): self.dist_map = None self.dose_map = None self.poly = None self.ssdec = 0 self.ssdep = 0 self.organ_dose_mean = 0 self.organ_dose_std = 0 self.diameter = 0 self.ssde = 0 self.ctdi = 0 self.ssdecs = {} self.ssdeps = {} self.means = {} self.stds = {} self.dist_maps = {} self.dose_maps = {} self.polys = {} def initModel(self): self.protocol_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.organ_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.organ_dose_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.protocol_model.setTable("Protocol") self.organ_model.setTable("Organ") self.organ_dose_model.setTable("Organ_Dose") self.protocol_model.setFilter("Group_ID=1") self.organ_dose_model.setFilter("Protocol_ID=1") self.protocol_model.select() self.organ_model.select() self.organ_dose_model.select() def sigConnect(self): self.method_cb.activated[int].connect(self.on_method_changed) self.protocol_cb.activated[int].connect(self.on_protocol_changed) self.calc_db_btn.clicked.connect(self.on_calculate_db) self.calc_cnt_btn.clicked.connect(self.on_calculate_cnt) self.add_cnt_btn.clicked.connect(self.on_contour) self.is_quick_mode_chk.stateChanged.connect(self.on_quick_mode_check) self.show_hk_chk.stateChanged.connect(self.on_show_hk_check) self.show_dosemap_chk.stateChanged.connect(self.on_show_dosemap_check) self.show_distmap_chk.stateChanged.connect(self.on_show_distmap_check) self.show_dosedist_chk.stateChanged.connect( self.on_show_dosedist_check) self.ctx.app_data.modeValueChanged.connect(self.diameter_mode_handle) # self.ctx.app_data.diameterValueChanged.connect(self.diameter_handle) # self.ctx.app_data.CTDIValueChanged.connect(self.ctdiv_handle) # self.ctx.app_data.SSDEValueChanged.connect(self.ssdew_handle) self.ctx.app_data.diametersUpdated.connect(self.update_values) self.ctx.app_data.ctdivsUpdated.connect(self.update_values) self.ctx.app_data.ssdesUpdated.connect(self.update_values) self.ctx.app_data.imgChanged.connect(self.img_changed_handle) self.ctx.axes.addPolyFinished.connect(self.add_cnt_handle) def initUI(self): self.figure = PlotDialog() self.method_cb = QComboBox() self.method_cb.addItems(['MC Data', 'Direct calculation']) self.init_db_method_ui() self.init_cnt_method_ui() self.main_area = QStackedWidget() self.main_area.addWidget(self.db_method_ui) self.main_area.addWidget(self.cnt_method_ui) self.on_method_changed() main_layout = QVBoxLayout() main_layout.addWidget(QLabel('Method:')) main_layout.addWidget(self.method_cb) main_layout.addWidget(self.main_area) main_layout.addStretch() self.setLayout(main_layout) def init_db_method_ui(self): self.protocol_cb = QComboBox() self.protocol_cb.setModel(self.protocol_model) self.protocol_cb.setModelColumn(self.protocol_model.fieldIndex('name')) self.calc_db_btn = QPushButton('Calculate') self.organ_labels = [] self.organ_edits = [QLineEdit('0') for i in range(28)] [organ_edit.setMaximumWidth(70) for organ_edit in self.organ_edits] [organ_edit.setReadOnly(True) for organ_edit in self.organ_edits] [ organ_edit.setAlignment(Qt.AlignRight) for organ_edit in self.organ_edits ] left = QFormLayout() right = QFormLayout() grid = QHBoxLayout() organ_grpbox = QGroupBox('Organ Dose') scroll = QScrollArea() for idx, organ_edit in enumerate(self.organ_edits): name = self.organ_model.record(idx).value('name') self.organ_names.append(name[0]) label = QLabel(name) label.setMaximumWidth(100) self.organ_labels.append(label) left.addRow(label, organ_edit) if idx < 14 else right.addRow( label, organ_edit) grid.addLayout(left) grid.addLayout(right) organ_grpbox.setLayout(grid) scroll.setWidget(organ_grpbox) scroll.setWidgetResizable(True) self.db_method_ui = QGroupBox('', self) db_method_layout = QVBoxLayout() db_method_layout.addWidget(QLabel('Protocol:')) db_method_layout.addWidget(self.protocol_cb) db_method_layout.addWidget(self.calc_db_btn) db_method_layout.addWidget(scroll) db_method_layout.addStretch() self.db_method_ui.setLayout(db_method_layout) def init_cnt_method_ui(self): self.calc_cnt_btn = QPushButton('Calculate') self.add_cnt_btn = QPushButton('Add Contour') self.is_quick_mode_chk = QCheckBox('Quick Mode') self.is_quick_mode_chk.setEnabled(False) self.show_dosemap_chk = QCheckBox('Show Dose Map') self.show_distmap_chk = QCheckBox('Show Distance Map') self.show_dosedist_chk = QCheckBox('Show Histogram') self.show_hk_chk = QCheckBox('Show Corr. Factor Graph') self.diameter_label = QLabel("<b>Diameter (cm)</b>") self.diameter_edit = QLineEdit('0') self.ctdiv_edit = QLineEdit('0') self.ssdew_edit = QLineEdit('0') self.ssdec_edit = QLineEdit('0') self.ssdep_edit = QLineEdit('0') self.mean_edit = QLineEdit('0') self.std_edit = QLineEdit('0') edits = [ self.diameter_edit, self.ctdiv_edit, self.ssdew_edit, self.ssdec_edit, self.ssdep_edit, self.mean_edit, self.std_edit ] [edit.setReadOnly(True) for edit in edits] self.diameter_mode_handle(DEFF_IMAGE) left = QGroupBox('', self) right = QGroupBox('', self) left_layout = QFormLayout() right_layout = QFormLayout() left_layout.addRow(self.diameter_label, self.diameter_edit) left_layout.addRow(QLabel("<b>CTDI<sub>vol</sub> (mGy)</b>"), self.ctdiv_edit) left_layout.addRow(QLabel("<b>SSDE<sub>w</sub> (mGy)</b>"), self.ssdew_edit) left_layout.addRow(QLabel("<b>SSDE<sub>c</sub> (mGy)</b>"), self.ssdec_edit) left_layout.addRow(QLabel("<b>SSDE<sub>p</sub> (mGy)</b>"), self.ssdep_edit) right_layout.addRow(QLabel("<b>Mean (mGy)</b>"), self.mean_edit) right_layout.addRow(QLabel("<b>Std. Deviation (mGy)</b>"), self.std_edit) left.setLayout(left_layout) right.setLayout(right_layout) output_area = QHBoxLayout() output_area.addWidget(left) output_area.addWidget(right) opt_grpbox = QGroupBox('Options') opt_area = QVBoxLayout() opt_area.addWidget(self.is_quick_mode_chk) opt_area.addWidget(self.show_distmap_chk) opt_area.addWidget(self.show_dosemap_chk) opt_area.addWidget(self.show_dosedist_chk) opt_area.addWidget(self.show_hk_chk) opt_grpbox.setLayout(opt_area) btn_layout = QVBoxLayout() btn_layout.addStretch() btn_layout.addWidget(self.add_cnt_btn) btn_layout.addWidget(self.calc_cnt_btn) btn_layout.addStretch() btn_opt_layout = QHBoxLayout() btn_opt_layout.addLayout(btn_layout) btn_opt_layout.addWidget(opt_grpbox) main_layout = QVBoxLayout() main_layout.addLayout(output_area) main_layout.addLayout(btn_opt_layout) self.cnt_method_ui = QGroupBox('', self) self.cnt_method_ui.setLayout(main_layout) def plot(self): xdict = dict(enumerate(self.organ_names, 1)) stringaxis = AxisItem(orientation='bottom') stringaxis.setTicks([xdict.items()]) # fm = QFontMetrics(stringaxis.font()) # minHeight = max(fm.boundingRect(QRect(), Qt.AlignLeft, t).width() for t in xdict.values()) # stringaxis.setHeight(minHeight + fm.width(' ')) self.figure = PlotDialog(size=(900, 600), straxis=stringaxis) self.figure.setTitle('Organ Dose') self.figure.axes.showGrid(False, True) self.figure.setLabels('', 'Dose', '', 'mGy') self.figure.bar(x=list(xdict.keys()), height=self.organ_dose_db, width=.8, brush='g') self.figure.show() def plot_cnt(self, dose_vec): self.figure = PlotDialog() self.figure.actionEnabled(True) self.figure.trendActionEnabled(False) self.figure.opts_dlg.y_mean_chk.setEnabled(False) self.figure.opts_dlg.y_stdv_chk.setEnabled(False) self.figure.histogram(dose_vec, fillLevel=0, brush=(0, 0, 255, 150), symbol='o', symbolSize=5) self.figure.axes.showGrid(True, True) self.figure.setLabels('Organ Dose', 'Frequency', 'mGy', '') self.figure.setTitle('Organ Dose') self.figure.show() def plot_hk(self): h, k, dw = self.get_interpolation() h_data = h(dw) k_data = k(dw) diameter = self.diameter c = h(diameter) p = k(diameter) anc_c = (1, 0) if diameter > 13.575512 else (1, 1) anc_p = (1, 1) if diameter > 13.575512 else (1, 0) self.figure_hk = PlotDialog() self.figure_hk.setTitle('Correction Factor') self.figure_hk.plot(dw, h_data, pen={ 'color': "FFFF00", 'width': 2 }, symbol=None, name='h_factor') self.figure_hk.plot(dw, k_data, pen={ 'color': "00FFFF", 'width': 2 }, symbol=None, name='k_factor') self.figure_hk.plot([diameter], [c], symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure_hk.plot([diameter], [p], symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure_hk.annotate( 'cfh', pos=(diameter, c), text=f'diameter = {diameter:#.2f}\nh-factor = {c:#.2f}', anchor=anc_c) self.figure_hk.annotate( 'cfk', pos=(diameter, p), text=f'diameter = {diameter:#.2f}\nk-factor = {p:#.2f}', anchor=anc_p) self.figure_hk.axes.showGrid(True, True) self.figure_hk.setLabels('Diameter', 'Correction Factor', 'mm', '') self.figure_hk.axes.setXRange(np.min(dw), np.max(dw)) self.figure_hk.axes.setYRange(np.min([np.min(k_data), np.min(h_data)]), np.max([np.max(k_data), np.max(h_data)])) self.figure_hk.show() def plot_dosemap(self): self.figure_dose = ImageViewDialog(unit='dose') self.figure_dose.setTitle('Dose Map') self.figure_dose.imshow(self.dose_map) self.figure_dose.show() def plot_img(self, img, title='figure', unit=(None, None)): key = title.lower().replace(' ', '_') self.fig[key] = ImageViewDialog(unit=unit) self.fig[key].setTitle(title) self.fig[key].imshow(img) self.fig[key].add_roi(self.poly) self.fig[key].show() def getData(self): self.alfas = np.array([ self.organ_dose_model.record(n).value('alfa') for n in range(self.organ_dose_model.rowCount()) ]) self.betas = np.array([ self.organ_dose_model.record(n).value('beta') for n in range(self.organ_dose_model.rowCount()) ]) def diameter_mode_handle(self, value): self.dist_map = None self.dose_map = None self.dist_maps = {} self.dose_maps = {} self.add_cnt_btn.setEnabled(True) self.add_cnt_btn.setText('Add Contour') if value == DW: self.diameter_label.setText('<b>Dw (cm)</b>') self.show_distmap_chk.setText('Show Water Eq. Distance Map') self.is_quick_mode_chk.setEnabled(True) else: self.diameter_label.setText('<b>Deff (cm)</b>') self.show_distmap_chk.setText('Show Effective Distance Map') self.is_quick_mode_chk.setCheckState(Qt.Unchecked) self.is_quick_mode_chk.setEnabled(False) def diameter_handle(self, value): self.diameter_edit.setText(f'{value:#.2f}') def ctdiv_handle(self, value): self.ctdiv_edit.setText(f'{value:#.2f}') def ssdew_handle(self, value): self.ssdew_edit.setText(f'{value:#.2f}') def add_cnt_handle(self, value): self.add_cnt_btn.setEnabled(True) self.calc_cnt_btn.setEnabled(True) def img_changed_handle(self, value): if value: self.update_values() def on_method_changed(self): self.main_area.setCurrentIndex(self.method_cb.currentIndex()) def on_protocol_changed(self, idx): self.protocol_id = self.protocol_model.record(idx).value("id") self.organ_dose_model.setFilter(f'Protocol_ID={self.protocol_id}') self.getData() print(self.protocol_id, self.protocol_model.record(idx).value("name")) def on_calculate_db(self): self.organ_dose_db = self.ctx.app_data.CTDIv * np.exp( self.alfas * self.ctx.app_data.diameter + self.betas) [ self.organ_edits[idx].setText(f'{dose:#.2f}') for idx, dose in enumerate(self.organ_dose_db) ] self.plot() def get_ssde(self): h, k, _ = self.get_interpolation() self.ssdec = h(self.diameter) * self.ssde self.ssdep = k(self.diameter) * self.ssde self.ssdec_edit.setText(f'{self.ssdec:#.2f}') self.ssdep_edit.setText(f'{self.ssdep:#.2f}') self.ssdecs[self.ctx.current_img] = self.ssdec self.ssdeps[self.ctx.current_img] = self.ssdep def build_dose_map(self): from functools import partial def avg_profile_line(p2, p1): x0, y0 = p1 x1, y1 = p2 length = int(np.hypot(x1 - x0, y1 - y0)) x = np.linspace(x0, x1, length).astype(int) y = np.linspace(y0, y1, length).astype(int) return img[x, y].mean() cancel = False rd = self.ctx.recons_dim row, col = self.ctx.get_current_img().shape mask = self.get_img_mask(self.ctx.get_current_img(), largest_only=True) if mask is not None: mask = mask.astype(float) mask_pos = np.argwhere(mask == 1) center = get_center(mask) dist_vec = np.sqrt(((mask_pos - center)**2).sum(1)) if self.ctx.app_data.mode == DW: img = self.ctx.get_current_img() profile_line_vec = np.zeros_like(dist_vec, dtype=float) n = mask_pos.shape[0] progress = QProgressDialog(f"Building dose map...", "Stop", 0, n, self) progress.setWindowModality(Qt.WindowModal) progress.setMinimumDuration(1000) profile_line = partial(avg_profile_line, center) for idx, pos in enumerate(mask_pos): profile_line_vec[idx] = profile_line(pos) progress.setValue(idx) if progress.wasCanceled(): cancel = True break progress.setValue(n) if np.isnan(profile_line_vec.sum()): profile_line_vec = np.nan_to_num(profile_line_vec) dist_vec *= ((profile_line_vec / 1000) + 1) if cancel: return dist_vec *= (0.1 * (rd / row)) dose_vec = ((dist_vec / ((self.diameter * 0.5) - 1)) * (self.ssdep - self.ssdec)) + self.ssdec self.dist_map = np.zeros_like(mask, dtype=float) self.dose_map = np.zeros_like(mask, dtype=float) self.dist_map[tuple(mask_pos.T)] = dist_vec self.dose_map[tuple(mask_pos.T)] = dose_vec self.dist_maps[self.ctx.current_img] = self.dist_map self.dose_maps[self.ctx.current_img] = self.dose_map def on_calculate_cnt(self): if self.ctx.axes.poly is None: QMessageBox.warning(None, "Warning", "Organ contour not found.") return self.poly = self.ctx.axes.poly self.polys[self.ctx.current_img] = self.poly self.get_ssde() if self.dose_map is None: self.build_dose_map() if self.dose_map is None: return organ_dose_map = self.poly.getArrayRegion(self.dose_map, self.ctx.axes.image, returnMappedCoords=False) organ_dose_mask_pos = np.argwhere(organ_dose_map != 0) organ_dose_vec = organ_dose_map[tuple(organ_dose_mask_pos.T)] self.organ_dose_mean = organ_dose_vec.mean() self.organ_dose_std = organ_dose_vec.std() self.means[self.ctx.current_img] = self.organ_dose_mean self.stds[self.ctx.current_img] = self.organ_dose_std self.mean_edit.setText(f'{self.organ_dose_mean:#.2f}') self.std_edit.setText(f'{self.organ_dose_std:#.2f}') if self.show_hk: self.plot_hk() if self.show_distmap: self.plot_img(self.dist_map, 'Distance Map', ('dist', 'cm')) if self.show_dosemap: self.plot_img(self.dose_map, 'Dose Map', ('dose', 'mGy')) if self.show_dosedist: self.plot_cnt(organ_dose_vec) def on_contour(self): print(self.ctx.axes.rois) if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return if self.ctx.axes.poly is None: self.ctx.axes.addPoly() self.add_cnt_btn.setText("Clear Contour") self.add_cnt_btn.setEnabled(False) self.calc_cnt_btn.setEnabled(False) else: self.ctx.axes.clearPoly() self.poly = None self.polys.pop(self.ctx.current_img, None) self.means.pop(self.ctx.current_img, None) self.stds.pop(self.ctx.current_img, None) self.organ_dose_mean = 0 self.organ_dose_std = 0 self.mean_edit.setText(f'{self.organ_dose_mean:#.2f}') self.std_edit.setText(f'{self.organ_dose_std:#.2f}') self.add_cnt_btn.setText("Add Contour") def get_organ_mask(self, roi, dose_map): img = roi.getArrayRegion(dose_map, self.ctx.axes.image, returnMappedCoords=False) return roi.renderShapeMask(img.shape[0], img.shape[1]) def get_img_mask(self, *args, **kwargs): mask = get_mask(*args, **kwargs) if mask is None: QMessageBox.warning( None, 'Segmentation Failed', 'No object found during segmentation process.') return mask def get_interpolation(self): arr = scio.loadmat(self.ctx.hk_data)['A'] dw = arr[0] hf = arr[5] kf = arr[11] h = interpolate.interp1d(dw, hf, kind='cubic') k = interpolate.interp1d(dw, kf, kind='cubic') return (h, k, dw) def on_quick_mode_check(self, state): self.is_quick_mode = state == Qt.Checked def on_show_dosemap_check(self, state): self.show_dosemap = state == Qt.Checked def on_show_distmap_check(self, state): self.show_distmap = state == Qt.Checked def on_show_dosedist_check(self, state): self.show_dosedist = state == Qt.Checked def on_show_hk_check(self, state): self.show_hk = state == Qt.Checked def update_values(self, val=True): if not val: return self.diameter = self.ctx.app_data.diameters[ self.ctx. current_img] if self.ctx.current_img in self.ctx.app_data.diameters.keys( ) else 0 self.ctdi = self.ctx.app_data.CTDIvs[ self.ctx. current_img] if self.ctx.current_img in self.ctx.app_data.CTDIvs.keys( ) else 0 self.ssde = self.ctx.app_data.SSDEs[ self.ctx. current_img] if self.ctx.current_img in self.ctx.app_data.SSDEs.keys( ) else 0 self.ssdec = self.ssdecs[ self.ctx.current_img] if self.ctx.current_img in self.ssdecs.keys( ) else 0 self.ssdep = self.ssdeps[ self.ctx.current_img] if self.ctx.current_img in self.ssdeps.keys( ) else 0 self.organ_dose_mean = self.means[ self.ctx.current_img] if self.ctx.current_img in self.means.keys( ) else 0 self.organ_dose_std = self.stds[ self.ctx.current_img] if self.ctx.current_img in self.stds.keys( ) else 0 self.dist_map = self.dist_maps[ self.ctx. current_img] if self.ctx.current_img in self.dist_maps.keys( ) else None self.dose_map = self.dose_maps[ self.ctx. current_img] if self.ctx.current_img in self.dose_maps.keys( ) else None if self.ctx.current_img in self.polys.keys(): self.poly = self.polys[self.ctx.current_img] self.ctx.axes.applyPoly(self.poly) self.add_cnt_btn.setText("Clear Contour") else: self.poly = None self.add_cnt_btn.setText("Add Contour") self.diameter_edit.setText(f'{self.diameter:#.2f}') self.ctdiv_edit.setText(f'{self.ctdi:#.2f}') self.ssdew_edit.setText(f'{self.ssde:#.2f}') self.ssdec_edit.setText(f'{self.ssdec:#.2f}') self.ssdep_edit.setText(f'{self.ssdep:#.2f}') self.mean_edit.setText(f'{self.organ_dose_mean:#.2f}') self.std_edit.setText(f'{self.organ_dose_std:#.2f}') def reset_fields(self): [organ_edit.setText('0') for organ_edit in self.organ_edits] self.protocol_cb.setCurrentIndex(0) self.on_protocol_changed(0) self.initVar() self.ctx.axes.cancel_addPoly() self.calc_cnt_btn.setEnabled(True) self.add_cnt_btn.setEnabled(True) self.add_cnt_btn.setText('Add Contour') self.update_values()
class FilteringComboBox(Widget): """Combination of QCombobox and QLineEdit with autocompletionself. Line edit and completer model is taken from QSqlTable mod Parameters: table (str): db table name containing data for combobox column (str): column name containing data for combobox color (str): 'rgb(r, g, b)' used for primary color font_size (int): default text font size in pt _model (QSqlTableModel): data model _col (int): display data model source coulumn _proxy (QSortFilterProxyModel): completer data model. _proxy.sourceModel() == _model _le (QLineEdit): QCombobox LineEdit Methods: createEditor(): (Widget): returns user input widgets value(): (str): returns user input text value setValue(value(str)): sets editor widget display value style(): (str): Returns CSS stylesheet string for input widget updateModel(): updates input widget model Args: table (str): db table name containing data for combobox column (str): column name containing data for combobox """ def __init__(self, parent, placeholderText, table, column, color='rgb(0,145,234)', image=''): self.table = table self.column = column self.color = color super().__init__(parent, placeholderText, image) self.updateModel() def createEditor(self): # setup data model self._model = QSqlTableModel() self._model.setTable(self.table) self._col = self._model.fieldIndex(self.column) # setup filter model for sorting and filtering self._proxy = QSortFilterProxyModel() self._proxy.setFilterCaseSensitivity(Qt.CaseInsensitive) self._proxy.setSourceModel(self._model) self._proxy.setFilterKeyColumn(self._col) # setup completer self._completer = QCompleter() self._completer.setModel(self._proxy) self._completer.setCompletionColumn(self._col) self._completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion) # setup combobox editor = QComboBox() editor.setModel(self._proxy) editor.setModelColumn(self._col) editor.setEditable(True) editor.setFocusPolicy(Qt.StrongFocus) editor.setInsertPolicy(QComboBox.NoInsert) editor.setCompleter(self._completer) # setup connections editor.currentTextChanged[str].connect(self.onActivated) # setup editor appearence style = self.style() editor.setStyleSheet(style) editor.lineEdit().setStyleSheet(style) font = editor.font() self._completer.popup().setFont(font) return editor @pyqtSlot(str) def onActivated(self, text): print('combo_box filter text', text) if not text: # placeholder text displayed and label is not visible self._editor.setCurrentIndex(-1) if self.toggle: self._label.showLabel(False) else: # selected text is displayed and label is visible # self._editor.showPopup() # self._editor.lineEdit().setFocus() print('current copmpletion string', self._completer.currentCompletion()) self._proxy.setFilterFixedString(text) if self.toggle: self._label.showLabel(True) def style(self): """Returns stylesheet for editors Returns: style (str) """ style = """ QLineEdit {{ border: none; padding-bottom: 2px; border-bottom: 1px solid rgba(0,0,0,0.42); background-color: white; color: rgba(0,0,0,0.42); font-size: {font_size}pt;}} QLineEdit:editable {{ padding-bottom: 2px; border-bottom: 1px rgba(0,0,0,0.42); color: rgba(0,0,0,0.42);}} QLineEdit:disabled {{ border: none; padding-bottom: 2px; border-bottom: 1px rgba(0,0,0,0.42); color: rgba(0,0,0,0.38);}} QLineEdit:hover {{ padding-bottom: 2px; border-bottom: 2px solid rgba(0,0,0,0.6); color: rgba(0,0,0,0.54); }} QLineEdit:focus {{ padding-bottom: 2px; border-bottom: 2px solid {color}; color: rgba(0,0,0,0.87);}} QLineEdit:pressed {{ border-bottom: 2px {color}; font: bold; color: rgba(0,0,0,0.87)}} QComboBox {{ border: none; padding-bottom: 2px; font-size: {font_size}pt; }} QComboBox::down-arrow {{ image: url('dropdown.png'); background-color: white; border: 0px white; padding: 0px; margin: 0px; height:14px; width:14px;}} QComboBox::drop-down{{ subcontrol-position: center right; border: 0px; margin: 0px; }} QComboBox QAbstractItemView {{ font: {font_size};}} """.format(color=self.color, font_size=self._editor_font_size, dropdown=DROPDOWN_PNG) return style def updateModel(self): model = self._editor.model().sourceModel() col = self._editor.modelColumn() model.select() model.sort(col, Qt.AscendingOrder) def value(self): return self._editor.currentText() def setValue(self, value): self._editor.setCurrentText(value)
class QmyMainWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) #调用父类构造函数,创建窗体 self.ui = Ui_MainWindow() #创建UI对象 self.ui.setupUi(self) #构造UI界面 self.setCentralWidget(self.ui.splitter) ## tableView显示属性设置 self.ui.tableView.setSelectionBehavior(QAbstractItemView.SelectItems) self.ui.tableView.setSelectionMode(QAbstractItemView.SingleSelection) self.ui.tableView.setAlternatingRowColors(True) self.ui.tableView.verticalHeader().setDefaultSectionSize(22) self.ui.tableView.horizontalHeader().setDefaultSectionSize(60) ## ==============自定义功能函数============ def __getFieldNames(self): ##获取所有字段名称 emptyRec = self.tabModel.record() #获取空记录,只有字段名 self.fldNum = {} #字段名与序号的字典 for i in range(emptyRec.count()): fieldName = emptyRec.fieldName(i) self.ui.comboFields.addItem(fieldName) self.fldNum.setdefault(fieldName) self.fldNum[fieldName] = i print(self.fldNum) def __openTable(self): ##打开数据表 self.tabModel = QSqlTableModel(self, self.DB) #数据模型 self.tabModel.setTable("employee") #设置数据表 self.tabModel.setEditStrategy(QSqlTableModel.OnManualSubmit ) #数据保存方式,OnManualSubmit , OnRowChange self.tabModel.setSort(self.tabModel.fieldIndex("empNo"), Qt.AscendingOrder) #排序 if (self.tabModel.select() == False): #查询数据失败 QMessageBox.critical( self, "错误信息", "打开数据表错误,错误信息\n" + self.tabModel.lastError().text()) return self.__getFieldNames() #获取字段名和序号 ##字段显示名 self.tabModel.setHeaderData(self.fldNum["empNo"], Qt.Horizontal, "工号") self.tabModel.setHeaderData(self.fldNum["Name"], Qt.Horizontal, "姓名") self.tabModel.setHeaderData(self.fldNum["Gender"], Qt.Horizontal, "性别") self.tabModel.setHeaderData(self.fldNum["Birthday"], Qt.Horizontal, "出生日期") self.tabModel.setHeaderData(self.fldNum["Province"], Qt.Horizontal, "省份") self.tabModel.setHeaderData(self.fldNum["Department"], Qt.Horizontal, "部门") self.tabModel.setHeaderData(self.fldNum["Salary"], Qt.Horizontal, "工资") self.tabModel.setHeaderData(self.fldNum["Memo"], Qt.Horizontal, "备注") #这两个字段不在tableView中显示 self.tabModel.setHeaderData(self.fldNum["Photo"], Qt.Horizontal, "照片") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("empNo"), Qt.Horizontal, "工号") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Name"), Qt.Horizontal, "姓名") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Gender"), Qt.Horizontal, "性别") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Birthday"), Qt.Horizontal, "出生日期") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Province"), Qt.Horizontal, "省份") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Department"),Qt.Horizontal, "部门") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Salary"), Qt.Horizontal, "工资") ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Memo"), Qt.Horizontal, "备注") #这两个字段不在tableView中显示 ## self.tabModel.setHeaderData(self.tabModel.fieldIndex("Photo"), Qt.Horizontal, "照片") ##创建界面组件与数据模型的字段之间的数据映射 self.mapper = QDataWidgetMapper() self.mapper.setModel(self.tabModel) #设置数据模型 self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) ##界面组件与tabModel的具体字段之间的联系 self.mapper.addMapping(self.ui.dbSpinEmpNo, self.fldNum["empNo"]) self.mapper.addMapping(self.ui.dbEditName, self.fldNum["Name"]) self.mapper.addMapping(self.ui.dbComboSex, self.fldNum["Gender"]) self.mapper.addMapping(self.ui.dbEditBirth, self.fldNum["Birthday"]) self.mapper.addMapping(self.ui.dbComboProvince, self.fldNum["Province"]) self.mapper.addMapping(self.ui.dbComboDep, self.fldNum["Department"]) self.mapper.addMapping(self.ui.dbSpinSalary, self.fldNum["Salary"]) self.mapper.addMapping(self.ui.dbEditMemo, self.fldNum["Memo"]) self.mapper.toFirst() #移动到首记录 self.selModel = QItemSelectionModel(self.tabModel) #选择模型 self.selModel.currentChanged.connect(self.do_currentChanged) #当前项变化时触发 self.selModel.currentRowChanged.connect( self.do_currentRowChanged) #选择行变化时 self.ui.tableView.setModel(self.tabModel) #设置数据模型 self.ui.tableView.setSelectionModel(self.selModel) #设置选择模型 self.ui.tableView.setColumnHidden(self.fldNum["Memo"], True) #隐藏列 self.ui.tableView.setColumnHidden(self.fldNum["Photo"], True) #隐藏列 ##tableView上为“性别”和“部门”两个字段设置自定义代理组件 strList = ("男", "女") self.__delegateSex = QmyComboBoxDelegate() self.__delegateSex.setItems(strList, False) self.ui.tableView.setItemDelegateForColumn( self.fldNum["Gender"], self.__delegateSex) #Combbox选择型 strList = ("销售部", "技术部", "生产部", "行政部") self.__delegateDepart = QmyComboBoxDelegate() self.__delegateDepart.setItems(strList, True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Department"], self.__delegateDepart) ##更新actions和界面组件的使能状态 self.ui.actOpenDB.setEnabled(False) self.ui.actRecAppend.setEnabled(True) self.ui.actRecInsert.setEnabled(True) self.ui.actRecDelete.setEnabled(True) self.ui.actScan.setEnabled(True) self.ui.groupBoxSort.setEnabled(True) self.ui.groupBoxFilter.setEnabled(True) ## ==========由connectSlotsByName() 自动连接的槽函数================== @pyqtSlot() ##选择数据库,打开数据表 def on_actOpenDB_triggered(self): dbFilename, flt = QFileDialog.getOpenFileName( self, "选择数据库文件", "", "SQL Lite数据库(*.db *.db3)") if (dbFilename == ''): return #打开数据库 self.DB = QSqlDatabase.addDatabase("QSQLITE") #添加 SQLITE数据库驱动 self.DB.setDatabaseName(dbFilename) #设置数据库名称 ## DB.setHostName() ## DB.setUserName() ## DB.setPassword() if self.DB.open(): #打开数据库 self.__openTable() #打开数据表 else: QMessageBox.warning(self, "错误", "打开数据库失败") @pyqtSlot() ##保存修改 def on_actSubmit_triggered(self): res = self.tabModel.submitAll() if (res == False): QMessageBox.information( self, "消息", "数据保存错误,错误信息\n" + self.tabModel.lastError().text()) else: self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) @pyqtSlot() ##取消修改 def on_actRevert_triggered(self): self.tabModel.revertAll() self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) @pyqtSlot() ##添加记录 def on_actRecAppend_triggered(self): self.tabModel.insertRow(self.tabModel.rowCount(), QModelIndex()) #在末尾添加一个记录 curIndex = self.tabModel.index(self.tabModel.rowCount() - 1, 1) #创建最后一行的ModelIndex self.selModel.clearSelection() #清空选择项 self.selModel.setCurrentIndex( curIndex, QItemSelectionModel.Select) #设置刚插入的行为当前选择行 currow = curIndex.row() #获得当前行 self.tabModel.setData(self.tabModel.index(currow, self.fldNum["empNo"]), 2000 + self.tabModel.rowCount()) #自动生成编号 self.tabModel.setData( self.tabModel.index(currow, self.fldNum["Gender"]), "男") @pyqtSlot() ##插入记录 def on_actRecInsert_triggered(self): curIndex = self.ui.tableView.currentIndex() #QModelIndex self.tabModel.insertRow(curIndex.row(), QModelIndex()) self.selModel.clearSelection() #清除已有选择 self.selModel.setCurrentIndex(curIndex, QItemSelectionModel.Select) @pyqtSlot() ##删除记录 def on_actRecDelete_triggered(self): curIndex = self.selModel.currentIndex() #获取当前选择单元格的模型索引 self.tabModel.removeRow(curIndex.row()) #删除当前行 @pyqtSlot() ##清除照片 def on_actPhotoClear_triggered(self): curRecNo = self.selModel.currentIndex().row() curRec = self.tabModel.record(curRecNo) #获取当前记录,QSqlRecord curRec.setNull("Photo") #设置为空值 self.tabModel.setRecord(curRecNo, curRec) self.ui.dbLabPhoto.clear() #清除界面上的图片显示 @pyqtSlot() ##设置照片 def on_actPhoto_triggered(self): fileName, filt = QFileDialog.getOpenFileName(self, "选择图片文件", "", "照片(*.jpg)") if (fileName == ''): return file = QFile(fileName) #fileName为图片文件名 file.open(QIODevice.ReadOnly) try: data = file.readAll() #QByteArray finally: file.close() curRecNo = self.selModel.currentIndex().row() curRec = self.tabModel.record(curRecNo) #获取当前记录QSqlRecord curRec.setValue("Photo", data) #设置字段数据 self.tabModel.setRecord(curRecNo, curRec) pic = QPixmap() pic.loadFromData(data) W = self.ui.dbLabPhoto.width() self.ui.dbLabPhoto.setPixmap(pic.scaledToWidth(W)) #在界面上显示 @pyqtSlot() ##涨工资,遍历数据表所有记录 def on_actScan_triggered(self): if (self.tabModel.rowCount() == 0): return for i in range(self.tabModel.rowCount()): aRec = self.tabModel.record(i) #获取当前记录 ## salary=aRec.value("Salary").toFloat() #错误,无需再使用toFloat()函数 salary = aRec.value("Salary") salary = salary * 1.1 aRec.setValue("Salary", salary) self.tabModel.setRecord(i, aRec) if (self.tabModel.submitAll()): QMessageBox.information(self, "消息", "涨工资计算完毕") @pyqtSlot(int) ##排序字段变化 def on_comboFields_currentIndexChanged(self, index): if self.ui.radioBtnAscend.isChecked(): self.tabModel.setSort(index, Qt.AscendingOrder) else: self.tabModel.setSort(index, Qt.DescendingOrder) self.tabModel.select() @pyqtSlot() ##升序 def on_radioBtnAscend_clicked(self): self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.AscendingOrder) self.tabModel.select() @pyqtSlot() ##降序 def on_radioBtnDescend_clicked(self): self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.DescendingOrder) self.tabModel.select() @pyqtSlot() ##过滤,男 def on_radioBtnMan_clicked(self): self.tabModel.setFilter("Gender='男'") ## print(self.tabModel.filter()) ## self.tabModel.select() @pyqtSlot() ##数据过滤,女 def on_radioBtnWoman_clicked(self): self.tabModel.setFilter("Gender='女' ") ## print(self.tabModel.filter()) ## self.tabModel.select() @pyqtSlot() ##取消数据过滤 def on_radioBtnBoth_clicked(self): self.tabModel.setFilter("") ## print(self.tabModel.filter()) ## self.tabModel.select() ## =============自定义槽函数=============================== def do_currentChanged(self, current, previous): ##更新actPost和actCancel 的状态 self.ui.actSubmit.setEnabled(self.tabModel.isDirty()) #有未保存修改时可用 self.ui.actRevert.setEnabled(self.tabModel.isDirty()) def do_currentRowChanged(self, current, previous): #行切换时的状态控制 self.ui.actRecDelete.setEnabled(current.isValid()) self.ui.actPhoto.setEnabled(current.isValid()) self.ui.actPhotoClear.setEnabled(current.isValid()) if (current.isValid() == False): self.ui.dbLabPhoto.clear() #清除图片显示 return self.mapper.setCurrentIndex(current.row()) #更新数据映射的行号 curRec = self.tabModel.record(current.row()) #获取当前记录,QSqlRecord类型 if (curRec.isNull("Photo")): #图片字段内容为空 self.ui.dbLabPhoto.clear() else: ## data=bytearray(curRec.value("Photo")) #可以工作 data = curRec.value("Photo") # 也可以工作 pic = QPixmap() pic.loadFromData(data) W = self.ui.dbLabPhoto.size().width() self.ui.dbLabPhoto.setPixmap(pic.scaledToWidth(W))
class QmyMainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setCentralWidget(self.ui.splitter) self.ui.tableView.setSelectionBehavior(QAbstractItemView.SelectItems) self.ui.tableView.setSelectionMode(QAbstractItemView.SingleSelection) self.ui.tableView.setAlternatingRowColors(True) self.ui.tableView.verticalHeader().setDefaultSectionSize(22) self.ui.tableView.horizontalHeader().setDefaultSectionSize(60) def __openTable(self): self.tabModel = QSqlTableModel(self, self.DB) self.tabModel.setTable("employee") self.tabModel.setEditStrategy(QSqlTableModel.OnManualSubmit) self.tabModel.setSort(self.tabModel.fieldIndex("empNo"), Qt.AscendingOrder) if(self.tabModel.select()==False): QMessageBox.critical(self, "错误信息", "打开数据表错误,错误信息\n"+self.tabModel.lastError().text()) return self.__getFieldNames() self.tabModel.setHeaderData(self.fldNum["empNo"], Qt.Horizontal, "工号") self.tabModel.setHeaderData(self.fldNum["Name"], Qt.Horizontal, "姓名") self.tabModel.setHeaderData(self.fldNum["Gender"], Qt.Horizontal, "性别") self.tabModel.setHeaderData(self.fldNum["Birthday"], Qt.Horizontal, "出生日期") self.tabModel.setHeaderData(self.fldNum["Province"], Qt.Horizontal, "省份") self.tabModel.setHeaderData(self.fldNum["Department"], Qt.Horizontal, "部门") self.tabModel.setHeaderData(self.fldNum["Salary"], Qt.Horizontal, "工资") self.tabModel.setHeaderData(self.fldNum["Memo"], Qt.Horizontal, "备注") self.tabModel.setHeaderData(self.fldNum["Photo"], Qt.Horizontal, "照片") self.mapper = QDataWidgetMapper() self.mapper.setModel(self.tabModel) self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) self.mapper.addMapping(self.ui.dbSpinEmpNo, self.fldNum["empNo"]) self.mapper.addMapping(self.ui.dbEditName, self.fldNum["Name"]) self.mapper.addMapping(self.ui.dbComboSex, self.fldNum["Gender"]) self.mapper.addMapping(self.ui.dbEditBirth, self.fldNum["Birthday"]) self.mapper.addMapping(self.ui.dbComboProvince, self.fldNum["Province"]) self.mapper.addMapping(self.ui.dbComboDep, self.fldNum["Department"]) self.mapper.addMapping(self.ui.dbSpinSalary, self.fldNum["Salary"]) self.mapper.addMapping(self.ui.dbEditMemo, self.fldNum["Memo"]) self.mapper.toFirst() self.selModel = QItemSelectionModel(self.tabModel) self.selModel.currentChanged.connect(self.do_currentChanged) self.selModel.currentRowChanged.connect(self.do_currentRowChanged) self.ui.tableView.setModel(self.tabModel) self.ui.tableView.setSelectionModel(self.selModel) self.ui.tableView.setColumnHidden(self.fldNum["Memo"], True) self.ui.tableView.setColumnHidden(self.fldNum["Photo"], True) strList = ("男", "女") self.__delegatesex = QmyComboBoxDelegate() self.__delegatesex.setItems(strList, False) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Gender"], self.__delegatesex) strList = ("销售部", "技术部", "生产部", "行政部") self.__delegateDepart = QmyComboBoxDelegate() self.__delegateDepart.setItems(strList, True) self.ui.tableView.setItemDelegateForColumn(self.fldNum["Department"], self.__delegateDepart) self.ui.actOpenDB.setEnabled(False) self.ui.actOpenDB.setEnabled(False) self.ui.actRecAppend.setEnabled(True) self.ui.actRecInsert.setEnabled(True) self.ui.actRecDelete.setEnabled(True) self.ui.actScan.setEnabled(True) self.ui.groupBoxSort.setEnabled(True) self.ui.groupBoxFilter.setEnabled(True) def __getFieldNames(self): emptyRec = self.tabModel.record() self.fldNum = {} for i in range(emptyRec.count()): fieldName = emptyRec.fieldName(i) self.ui.comboFields.addItem(fieldName) self.fldNum.setdefault(fieldName) self.fldNum[fieldName]=i print(self.fldNum) def do_currentChanged(self, current, previous): self.ui.actSubmit.setEnabled(self.tabModel.isDirty()) self.ui.actRevert.setEnabled(self.tabModel.isDirty()) def do_currentRowChanged(self, current, previous): self.ui.actRecDelete.setEnabled(current.isValid()) self.ui.actPhoto.setEnabled(current.isValid()) self.ui.actPhotoClear.setEnabled(current.isValid()) if(current.isValid() == False): self.ui.dbLabPhoto.clear() return self.mapper.setCurrentIndex(current.row()) curRec = self.tabModel.record(current.row()) if(curRec.isNull("Photo")): self.ui.dbLabPhoto.clear() else: data = curRec.value("Photo") pic = QPixmap() pic.loadFromData(data) w = self.ui.dbLabPhoto.size().width() self.ui.dbLabPhoto.setPixmap(pic.scaledToWidth(w)) @pyqtSlot() def on_actOpenDB_triggered(self): dbFilename ,flt = QFileDialog.getOpenFileName(self, "选择数据库文件", "", "SQL Lite数据库(*.db *.db3)") if (dbFilename == ''): return self.DB = QSqlDatabase.addDatabase("QSQLITE") self.DB.setDatabaseName(dbFilename) if self.DB.open(): self.__openTable() else: QMessageBox.warning(self, "错误", "打开数据库失败") @pyqtSlot() def on_actSubmit_triggered(self): res = self.tabModel.submitAll() if(res == False): QMessageBox.information(self, "消息", "数据保存错误,错误信息\n" + self.tabModel.lastError().text()) else: self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) @pyqtSlot() def on_actRevert_triggered(self): self.tabModel.revertAll() self.ui.actSubmit.setEnabled(False) self.ui.actRevert.setEnabled(False) @pyqtSlot() def on_actRecAppend_triggered(self): self.tabModel.insertRow(self.tabModel.rowCount(), QModelIndex()) curIndex = self.tabModel.index(self.tabModel.rowCount()-1, 1) self.selModel.clearSelection() self.selModel.setCurrentIndex(curIndex, QItemSelectionModel.Select) currow = curIndex.row() self.tabModel.setData(self.tabModel.index(currow, self.fldNum["empNo"]), 2000+self.tabModel.rowCount()) self.tabModel.setData(self.tabModel.index(currow, self.fldNum["Gender"]), "男") @pyqtSlot() def on_actRecInsert_triggered(self): curIndex = self.ui.tableView.currentIndex() self.tabModel.insertRow(curIndex.row(), QModelIndex()) self.selModel.clearSelection() self.selModel.setCurrentIndex(curIndex, QItemSelectionModel.Select) @pyqtSlot() def on_actRecDelete_triggered(self): curIndex = self.selModel.currentIndex() self.tabModel.removeRow(curIndex.row()) @pyqtSlot() def on_actPhotoClear_triggered(self): curRecNo = self.selModel.currentIndex().row() curRec = self.tabModel.record(curRecNo) curRec.setNull("Photo") self.tabModel.setRecord(curRecNo, curRec) self.ui.dbLabPhoto.clear() @pyqtSlot() def on_actPhoto_triggered(self): fileName, filt = QFileDialog.getOpenFileName(self, "选择图片文件", "", "照片(*.jpg") if(fileName==''): return file=QFile(fileName) file.open(QIODevice.ReadOnly) try: data = file.readAll() finally: file.close() curRecNo = self.selModel.currentIndex().row() curRec = self.tabModel.record(curRecNo) curRec.setValue("Photo", data) self.tabModel.setRecord(curRecNo, curRec) pic = QPixmap() pic.loadFromData(data) w = self.ui.dbLabPhoto.width() self.ui.dbLabPhoto.setPixmap(pic.scaledToWidth(w)) @pyqtSlot() def on_actScan_triggered(self): if(self.tabModel.rowCount()==0): return for i in range(self.tabModel.rowCount()): aRec = self.tabModel.record(i) salary = aRec.value("Salary") salary = salary*1.1 aRec.setValue("Salary", salary) self.tabModel.setRecord(i, aRec) if(self.tabModel.submitAll()): QMessageBox.information(self, "消息", "涨工资计算完毕了") @pyqtSlot() def on_comboFields_currentIndexChanged(self, index): if self.ui.radioBtnAscend.isChecked(): self.tabModel.setSort(index, Qt.AscendingOrder) else: self.tabModel.setSort(index, Qt.DescendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnAscend_clicked(self): self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.AscendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnDescend_clicked(self): self.tabModel.setSort(self.ui.comboFields.currentIndex(), Qt.DescendingOrder) self.tabModel.select() @pyqtSlot() def on_radioBtnMan_clicked(self): self.tabModel.setFilter("Gender='男'") @pyqtSlot() def on_radioBtnWoman_clicked(self): self.tabModel.setFilter("Gender='女'") @pyqtSlot() def on_radioBtnBoth_clicked(self): self.tabModel.setFilter("")
class ScannerDataDialog(QDialog): def __init__(self, ctx, *args, **kwargs): super(ScannerDataDialog, self).__init__(*args, **kwargs) self.ctx = ctx self.setWindowTitle("Add Scanner Data") self.initUI() self.initModel() self.signal_connect() def signal_connect(self): self.exist_scanner_chk.stateChanged.connect(self.on_exist_scanner) self.exist_brand_chk.stateChanged.connect(self.on_exist_brand) self.buttons.accepted.connect(self.on_save) self.buttons.rejected.connect(self.on_close) self.brand_cb.activated[int].connect(self.on_brand_changed) self.scanner_cb.activated[int].connect(self.on_scanner_changed) [ btn.toggled.connect(self.on_phantom_select) for btn in self.phantom_rbs ] def initModel(self): self.brand_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.scanner_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.volt_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.coll_query = QSqlTableModel(db=self.ctx.database.ctdi_db) # fill brand combobox self.brand_query.setTable("BRAND") self.brand_query.select() self.brand_id = self.brand_query.record(0).value("ID") # fill scanner combobox self.scanner_query.setTable("SCANNER") self.scanner_query.setFilter("BRAND_ID=1") self.scanner_query.select() self.scanner_id = self.scanner_query.record(0).value("ID") # fill voltage combobox self.volt_query.setTable("CTDI_DATA") self.volt_query.setFilter("SCANNER_ID=1") self.volt_query.select() self.CTDI = self.volt_query.record(0).value("CTDI_HEAD") # fill collimation combobox self.coll_query.setTable("COLLIMATION_DATA") self.coll_query.setFilter("SCANNER_ID=1") self.coll_query.select() self.coll = self.coll_query.record(0).value("VALUE") self.brand_cb.setModel(self.brand_query) self.brand_cb.setModelColumn(self.brand_query.fieldIndex("NAME")) self.scanner_cb.setModel(self.scanner_query) self.scanner_cb.setModelColumn(self.scanner_query.fieldIndex("NAME")) self.on_brand_changed(0) def initUI(self): btns = QDialogButtonBox.Save | QDialogButtonBox.Close self.buttons = QDialogButtonBox(btns) self.brand_cb = QComboBox() self.scanner_cb = QComboBox() self.brand_edit = QLineEdit() self.scanner_edit = QLineEdit() self.volt_edit = QLineEdit() self.coll_edit = QLineEdit() self.ctdih_edit = QTextEdit() self.ctdib_edit = QTextEdit() self.ctdi_edits_layout = QStackedWidget(self) self.ctdi_edits_layout.addWidget(self.ctdih_edit) self.ctdi_edits_layout.addWidget(self.ctdib_edit) self.phantom_rbs = [QRadioButton('Head'), QRadioButton('Body')] self.phantom_rbs[0].setChecked(True) self.exist_brand_chk = QCheckBox('Add existing') self.exist_scanner_chk = QCheckBox('Add existing') self.exist_scanner_chk.setEnabled(False) self.brand_edits_layout = QStackedWidget() self.brand_edits_layout.addWidget(self.brand_edit) self.brand_edits_layout.addWidget(self.brand_cb) self.scanner_edits_layout = QStackedWidget() self.scanner_edits_layout.addWidget(self.scanner_edit) self.scanner_edits_layout.addWidget(self.scanner_cb) self.rb_layout = QHBoxLayout() [self.rb_layout.addWidget(btn) for btn in self.phantom_rbs] self.rb_layout.addStretch() self.main_widget = QWidget() self.inner_layout = QFormLayout() self.inner_layout.addRow(QLabel('Manufacturer'), self.brand_edits_layout) self.inner_layout.addRow(QLabel(''), self.exist_brand_chk) self.inner_layout.addRow(QLabel('Scanner'), self.scanner_edits_layout) self.inner_layout.addRow(QLabel(''), self.exist_scanner_chk) self.inner_layout.addRow(QLabel('Voltage'), self.volt_edit) self.inner_layout.addRow(QLabel('Collimation'), self.coll_edit) self.inner_layout.addRow(QLabel('Phantom'), self.rb_layout) self.inner_layout.addRow(QLabel('CTDIw'), self.ctdi_edits_layout) self.main_widget.setLayout(self.inner_layout) self.main_layout = QVBoxLayout() self.main_layout.addWidget(self.main_widget) self.main_layout.addWidget(self.buttons) self.setLayout(self.main_layout) # txt = 'Comma-separated values for each collimation for each ' # self.ctdib_edit.setPlaceholderText('body') # self.ctdih_edit.setPlaceholderText('head') def on_exist_brand(self, state): self.brand_edits_layout.setCurrentIndex(int(state == Qt.Checked)) self.exist_scanner_chk.setCheckState(Qt.Unchecked) self.exist_scanner_chk.setEnabled(state == Qt.Checked) def on_exist_scanner(self, state): self.scanner_edits_layout.setCurrentIndex(int(state == Qt.Checked)) def on_brand_changed(self, sel): self.brand_id = self.brand_query.record(sel).value("ID") self.scanner_query.setFilter(f"BRAND_ID={self.brand_id}") self.on_scanner_changed(0) self.scanner_items = [ self.scanner_cb.itemText(i).lower() for i in range(self.scanner_cb.count()) ] def on_scanner_changed(self, sel): self.scanner_id = self.scanner_query.record(sel).value("ID") self.volt_query.setFilter(f"SCANNER_ID={self.scanner_id}") self.coll_query.setFilter(f"SCANNER_ID={self.scanner_id}") # if self.volt_query.rowCount()>0: # self.volt_items = [float(self.volt_cb.itemText(i)) for i in range(self.volt_cb.count())] # if self.coll_query.rowCount()>0: # self.coll_items = [float(self.coll_query.record(i).value("COL_VAL")) for i in range(self.coll_cb.count())] def on_phantom_select(self): sel = self.sender() if sel.isChecked(): self.phantom = sel.text().lower() self.ctdi_edits_layout.setCurrentIndex(int(self.phantom == 'body')) def on_save(self): # crazy stuffs btn_reply = QMessageBox.question(self, 'Add more data', 'Do you want to add more data?') self.reset_fields() if btn_reply == QMessageBox.No: self.accept() def on_close(self): self.reset_fields() self.reject() def reset_fields(self): self.brand_edit.setText('') self.scanner_edit.setText('') self.volt_edit.setText('') self.coll_edit.setText('') self.ctdih_edit.setText('') self.exist_brand_chk.setCheckState(Qt.Unchecked) self.exist_scanner_chk.setCheckState(Qt.Unchecked)
class DBControl(QObject, SqlalchemyDBControl, DB_Util1): def __init__(self, parent=None): super().__init__() import Client self.mw: Client.MainWindow = parent self.__initDB() self.__initUI() def __initDB(self): self._initDB() self._createDB() self._createModel() self._selectModel() # <editor-fold desc="qtdb"> def _initDB(self): self.orm_db = self.create_session() self.qt_db = QSqlDatabase.addDatabase('QSQLITE') self.qt_db.setDatabaseName(self.db_path) if not self.qt_db.open(): print("数据库连接失败") return False return self.qt_db def _createDB(self): self.create_table() print(self.qt_db.database().tables()) def _createModel(self): def createModel(tbName): model = QSqlTableModel() # model.setEditStrategy(QSqlTableModel.OnFieldChange) model.setTable(tbName) model.select() setattr( self, tbName + "_model", model ) # here will create [self.watching_show_model ... ] and other model return model def createView(title, model, delegate=None): view = QQTableView(self.mw) view.setModel(model) view.setItemDelegateForColumn( 0, delegate) if delegate is not None else None view.REMOVE_SIGNAL.connect(self.del_watching) return view def createTab(title): tab_form = self.mw.repo_tabWidget new_tab = QWidget() layout = QHBoxLayout(new_tab) layout.setContentsMargins(1, 1, 1, 1) tab_form.addTab(new_tab, title) return layout def createItem(title): comb = self.mw.filter_cb comb.addItem(title) tables = self.qt_db.database().tables() for tableName in tables: if tableName[-4:] == "show": # print(tableName) model = createModel(tableName) delegate = URLDelegate(self) view_ = createView(tableName, model, delegate) new_tab = createTab(tableName) _ = createItem(tableName) new_tab.addWidget(view_) # modeln self.filterModel = QSqlTableModel() # model1 self.urlTableModel = QSqlTableModel() self.urlTableModel.setTable(WEBSITE_TABLE_NAME) # model2 self.unameTableModel = QSqlTableModel() self.unameTableModel.setTable(USER_TABLE_NAME) if isinstance( self.unameTableModel, QSqlTableModel) else None # model3 self.upwdQueryModel = QSqlQueryModel(self) """ ###self.url_mapper = QDataWidgetMapper() # 数据映射 ###self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) # 提交策略 ###self.url_mapper.setModel(self.urlQueryModel) # 映射的模型源 ###self.url_mapper.addMapping(self.mw.web_site_cb,0, b"currentIndex") ###self.mw.web_site_cb.setModel(self.urlQueryModel) # model4 self.realationModel = QSqlRelationalTableModel(self) self.realationModel.setEditStrategy(QSqlTableModel.OnFieldChange) self.realationModel.setTable(REPOSITORIES_TABLE_NAME) self.realationModel.setRelation(1, QSqlRelation(WEBSITE_TABLE_NAME, 'id', 'url')) # TW_TITLE = [""] # for i in range(len(TW_TITLE)): # self.realationModel.setHeaderData(i + 1, Qt.Horizontal, TW_TITLE[i]) self.realationModel.select() """ def _selectModel(self): # <editor-fold desc="1"> self.mw.web_site_cb.setModel(self.urlTableModel) # self.mw.web_user_cb.setModelColumn(1) # </editor-fold> # <editor-fold desc="2"> self.mw.web_user_cb.setModel(self.unameTableModel) self.mw.web_user_cb.setModelColumn(1) # </editor-fold> # <editor-fold desc="3"> self.upwd_mapper = QDataWidgetMapper() # 数据映射 self.upwd_mapper.setModel(self.upwdQueryModel) # 映射的模型源 self.upwd_mapper.addMapping(self.mw.web_pwd_le, 0) ## self.mapper.setSubmitPolicy(QDataWidgetMapper.AutoSubmit) # 提交策略 # </editor-fold> # <editor-fold desc="4"> self.mw.filter_tv.setModel(self.filterModel) self.mw.filter_tv.setItemDelegateForColumn(0, URLDelegate()) # </editor-fold> # </editor-fold> def __initUI(self): self._pre_url_ = "---" self._pre_uname_ = "---" # 1 . self.mw.web_site_cb.activated.connect(self.on_web_uname_query) self.mw.web_user_cb.activated.connect(self.on_web_upwd_query) def on_web_site_url_query(self): print("查网址") # self.urlTableModel.setQuery(QSqlQuery(self.ORM2SQL(select([WebSite.url])))) self.reloadTableModel(self.urlTableModel, False) self.mw.web_site_cb.setModelColumn(1) # 如果没有item, setCurrentIndex(0) #qtbug 是不起作用的, index还是-1 self.mw.web_site_cb.setCurrentIndex(0) self.on_web_uname_query() def on_web_uname_query(self, index=None): """ 网址索引改变的时候触发. :return: """ print("查用户") c_url = self.get_c_url() curl_id = self.get_curl_id(c_url) self.mw.web_pwd_le.setText("") # 有结果 if curl_id is not None: print("查到用户") self.unameTableModel.setFilter(f"""user_info.url_id = {curl_id}""") self.reloadTableModel(self.unameTableModel) self.mw.web_user_cb.setCurrentIndex(0) self.on_web_upwd_query() # else: def on_web_upwd_query(self): print("查密码") c_url, c_uname, c_upwd, curl_id = self.get_c_all() self.upwdQueryModel.setQuery( QSqlQuery( self.ORM2SQL( select([UserTable.upwd]).where( and_(UserTable.url_id == curl_id, UserTable.uname == c_uname))))) # 设置密码UI self.upwd_mapper.toFirst() # loadtask self._set_pre_cb_text(c_url, c_uname) # -------------user------------- ↓ def create_or_update_webInfo(self): c_url, c_uname, c_upwd, curl_id = self.get_c_all() if curl_id is None: model = self.urlTableModel row = model.rowCount() ret = model.insertRows(row, 1) print('insertRows=%s' % str(ret), model.columnCount()) record_count = model.record().count() print(record_count) for col in range(model.columnCount()): index = model.index(row, col) # record = model.record(row) # field = record.field(col) field_col = self.urlTableModel.fieldIndex("url") if col == field_col: model.setData(index, c_url) self.urlTableModel.submit() self.mw.web_site_cb.setModelColumn(1) # self.urlTableModel.select() # qt bug # web_obj = WebSite(url=c_url) # self.orm_db.add(web_obj) # self.orm_db.flush() # curl_id = web_obj.id curl_id = model.index(row, 0).data() # 法一: 双主键, 有则更新 , 无则创建 # self.orm_db.merge(User(uname=c_uname, upwd=c_upwd, url_id=curl_id)) # self.orm_db.commit() # 改变索引 # self.reloadTableModel(self.urlTableModel) # self.reloadTableModel(self.unameTableModel) ## 法二 model = self.unameTableModel row = model.rowCount() ret = model.insertRows(row, 1) print('insertRows=%s' % str(ret), model.columnCount()) for col in range(model.columnCount()): index = model.index(row, col) if col == 0: model.setData(index, curl_id) elif col == 1: model.setData(index, c_uname) elif col == 2: model.setData(index, c_upwd) elif col == 3: model.setData(index, "{}") self.unameTableModel.submitAll() def delete_webInfo(self): """ :return: """ c_url, c_uname, c_upwd, curl_id = self.get_c_all() # 找到网址 if curl_id is not None: # 删网址 if c_uname == '' and c_upwd == '': obj = self.orm_db.query(WebSiteTable).filter_by( url=c_url).one() self.orm_db.delete(obj) self.orm_db.commit() self.on_web_site_url_query() # 删账户 else: try: obj = self.orm_db.query(UserTable).filter_by( uname=c_uname, upwd=c_upwd, url_id=curl_id).one() self.orm_db.delete(obj) self.orm_db.commit() self.mw.web_pwd_le.setText("") self.reloadTableModel(self.unameTableModel, False) # self.mw.web_user_cb.setCurrentIndex(0) except: pass # -------------user------------- ↑ # -------------table query------------- ↓ def get_watching(self): # try: WatchIterator = github.get_watching(self) self.mw.thread_pools.spawn(self.get_watching_querydb, WatchIterator) def get_watching_querydb(self, WatchIterator): for watched_repo in WatchIterator: query_field = "/" + watched_repo.full_name WatchingTable_obj: WatchingTable = self.orm_db.query( WatchingTable).filter_by(query_field=query_field).first() if WatchingTable_obj is None: uname = self.get_c_uname() self.orm_db.add( WatchingTable(query_field=query_field, uname=uname)) self.orm_db.commit() model: QSqlTableModel = self.watching_show_model model.select() # TODO:重新载入, 可能可以改成局部刷新. else: pass def del_watching(self, tv, data_dict): data = data_dict.get("data") # type:list del_watching_repo = data[0] print(f"{del_watching_repo}") print(self.get_c_tableName()) if self.get_c_tableName() == WATCHING_TABLE_NAME: print(f"delete") github.del_watching(del_watching_repo) else: pass # TODO:查数据库 ,可能用协程 def re_select_model(self): model = self.get_c_model() model.select() def filterAll(self): """ 过滤 :return: """ query_fields = self.mw.filter_le.text().strip() # 小注释 : e.g."py test " query_field_list = query_fields.split(" ") # 小注释 : space split tableName = self.mw.filter_cb.currentText() # 小注释 : 单表 if tableName != "All": TableClass = getTable(tableName) ##type:BaseComment self.filterModel.setQuery( QSqlQuery( self.ORM2SQL( select([TableClass]).where( or_(*tuple( map( lambda query_field: TableClass.comment. like(f'%{query_field}%'), query_field_list)) # 小注释 : # TableClass.comment.like(f'%{query_field_list[0]}%'), # TableClass.comment.like(f'%{query_field_list[1]}%'), # TableClass.comment.like(f'%{query_field_list[2]}%'), ))))) # 小注释 : 多表 union else: tw = self.mw.repo_tabWidget tabCount = tw.count() end = tabCount - 1 sql = "" for i in range(tabCount): tableName = tw.tabText(i) condition = " OR ".join( list( map( lambda query_field: f"""{tableName}.comment LIKE '%{query_field}%' """, query_field_list))) sql += f"""SELECT * From {tableName} WHERE {condition}""" if i != end: sql += " UNION ALL " if self.mw.filter_ckBox.isChecked( ) else " UNION " # print(sql) self.filterModel.setQuery(QSqlQuery(sql)) # -------------table query------------- ↑ def ORM2SQL(self, statement): """ :param statement: :return: """ query = statement.compile(dialect=sqlite.dialect()) query_str = str(query) query_paras: dict = query.params query_raw_sql = query_str.replace('?', r"%r") % tuple( query_paras.values()) # print("==query_raw_sql==↓:") # print(query_raw_sql) # print("==query_raw_sql==↑:") return query_raw_sql
class Filtrage_bope_dialog(QDialog, Ui_dlgBopeRechercheForm): ''' Class de la fenêtre permettant le filtrage attributaire des baux de pêche :param QDialog: Permet d'afficher l'interface graphique comme une fenêtre indépendante :type QDialog: QDialog :param Ui_dlgBopeRechercheForm: Class du script de l'interface graphique du formulaire, apporte les éléments de l'interface :type Ui_dlgBopeRechercheForm: class ''' def __init__(self, db, dbType, dbSchema, modelBauxPe, parent=None): ''' Constructeur, récupération de variable, connection des événements et remplissage des combobox :param db: définie dans le setupModel(), représente la connexion avec la base de données :type db: QSqlDatabase :param dbType: type de la base de données (postgre) :type dbType: str :param dbSchema: nom du schéma sous PostgreSQL contenant les données (data) :type dbSchema: unicode :param modelBauxPe: modèle droit de pêche qui contient les données de la base de données :type modelBauxPe: QSqlRelationalTableModel :param parent: défini que cette fenêtre n'hérite pas d'autres widgets :type parent: NoneType ''' super(Filtrage_bope_dialog, self).__init__(parent) self.db = db self.dbType = dbType self.dbSchema = dbSchema self.modelBauxPe = modelBauxPe self.setupUi(self) self.btnAnnuler.clicked.connect(self.reject) self.btnExec.clicked.connect(self.execution) self.btnRaz.clicked.connect(self.raz) self.btnEt.clicked.connect(self.et) self.btnOu.clicked.connect(self.ou) self.btnPrevisualiser.clicked.connect(self.previSql) self.btnId.clicked.connect(self.ajoutId) self.btnRiviere.clicked.connect(self.ajoutRiviere) self.btnAappma.clicked.connect(self.ajoutAappma) self.btnPossession.clicked.connect(self.ajoutPossession) self.btnSign.clicked.connect(self.ajoutDateSign) self.btnFin.clicked.connect(self.ajoutDateFin) self.btnC.clicked.connect(self.ajoutCommune) self.btnCS.clicked.connect(self.ajoutComSection) self.btnCSP.clicked.connect(self.ajoutComSecParcelle) self.btnProprio.clicked.connect(self.ajoutProprio) self.btnAdresse.clicked.connect(self.ajoutAdresse) self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.leTel.setInputMask("#9999999999999") self.possessionBool = False self.aappmaBool = False self.anneeSignBool = False self.anneeFinBool = False self.CBool = False self.CSBool = False self.CSPBool = False self.wwhere = "" self.wwherePossession = "" self.wwhereProprio = "" self.modelAappma = QSqlTableModel(self, self.db) wrelation = "aappma" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelAappma.setTable(wrelation) self.modelAappma.setSort(1, Qt.AscendingOrder) if (not self.modelAappma.select()): QMessageBox.critical(self, u"Remplissage du modèle AAPPMA", self.modelAappma.lastError().text(), QMessageBox.Ok) self.cmbAappma.setModel(self.modelAappma) self.cmbAappma.setModelColumn(self.modelAappma.fieldIndex("apma_nom")) self.modelRiviere = QSqlTableModel(self, self.db) wrelation = "cours_eau" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelRiviere.setTable(wrelation) self.modelRiviere.setFilter("ceau_nom <> 'NR'") self.modelRiviere.setSort(2, Qt.AscendingOrder) if (not self.modelRiviere.select()): QMessageBox.critical(self, u"Remplissage du modèle Rivière", self.modelRiviere.lastError().text(), QMessageBox.Ok) self.cmbRiviere.setModel(self.modelRiviere) self.cmbRiviere.setModelColumn( self.modelRiviere.fieldIndex("ceau_nom")) self.modelCommune = QSqlTableModel(self, self.db) wrelation = "commune" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelCommune.setTable(wrelation) self.modelCommune.setSort(2, Qt.AscendingOrder) if (not self.modelCommune.select()): QMessageBox.critical(self, u"Remplissage du modèle Commune", self.modelCommune.lastError().text(), QMessageBox.Ok) self.cmbCommune.setModel(self.modelCommune) self.cmbCommune.setModelColumn(self.modelCommune.fieldIndex("com_nom")) self.cmbCommune.setCurrentIndex(1) self.modelSection = QSqlQueryModel(self) self.modelParcelle = QSqlQueryModel(self) self.cmbSection.setModel(self.modelSection) self.cmbSection.setModelColumn(2) self.cmbParcelle.setModel(self.modelParcelle) self.cmbParcelle.setModelColumn(1) self.cmbCommune.currentIndexChanged.connect(self.changeCmbCommune) self.cmbSection.currentIndexChanged.connect(self.changeCmbSection) self.cmbCommune.setCurrentIndex(0) def reject(self): '''Ferme la fenêtre si clic sur le bouton annuler''' QDialog.reject(self) def changeCmbCommune(self, newInd): ''' Filtre la combobox section en fonction de la commune affichée dans celle des communes :param newInd: index courant dans la combobox :type newInd: int ''' self.modelParcelle.clear() self.cmbParcelle.clear() self.cmbParcelle.setModel(self.modelParcelle) self.cmbParcelle.setModelColumn(1) record = self.modelCommune.record(newInd) wcom_insee = record.value(self.modelCommune.fieldIndex("com_insee")) self.modelSection.clear() wrelation = "section" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelSection.setQuery( "select sec_id, sec_nom || ' ; ' || sec_com_abs from " + wrelation + " where sec_com_insee = '%s' order by sec_nom;" % str(wcom_insee), self.db) if self.modelSection.lastError().isValid(): QMessageBox.critical(self, u"Remplissage du modèle Section", self.modelSection.lastError().text(), QMessageBox.Ok) self.cmbSection.setModel(self.modelSection) self.cmbSection.setModelColumn(1) self.cmbSection.setCurrentIndex(0) def changeCmbSection(self, newInd): ''' Filtre la combobox parcelle en fonction de la section affichée dans celle des sections :param newInd: index courant dans la combobox :type newInd: int ''' record = self.modelSection.record(newInd) wsec_id = record.value(0) self.cmbParcelle.clear() self.modelParcelle.clear() wrelation = "parcelle" if self.dbType == "postgres": wrelation = self.dbSchema + "." + wrelation self.modelParcelle.setQuery( "select par_id, par_numero from " + wrelation + " where par_sec_id = '%s' order by par_numero;" % str(wsec_id), self.db) if self.modelParcelle.lastError().isValid(): QMessageBox.critical(self, u"Remplissage du modèle Parcelle", self.modelParcelle.lastError().text(), QMessageBox.Ok) self.cmbParcelle.setModel(self.modelParcelle) self.cmbParcelle.setModelColumn(1) def raz(self): '''Réinitialise toutes les variables de la fenêtre afin de recommencer une nouvelle requête''' self.possessionBool = False self.aappmaBool = False self.anneeSignBool = False self.anneeFinBool = False self.CBool = False self.CSBool = False self.CSPBool = False self.spnId.setValue(0) self.wrq = "" self.txtSql.setText("") self.wwhere = "" self.wwherePossession = "" self.wwhereProprio = "" self.chkPossession.setChecked(False) self.dateSign.setDate(QDate(2000, 1, 1)) self.dateFin.setDate(QDate(2000, 1, 1)) self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnId.setEnabled(True) self.btnSign.setEnabled(True) self.btnFin.setEnabled(True) self.btnRiviere.setEnabled(True) self.btnAappma.setEnabled(True) self.btnPossession.setEnabled(True) self.btnC.setEnabled(True) self.btnCS.setEnabled(True) self.btnCSP.setEnabled(True) self.btnProprio.setEnabled(True) self.btnAdresse.setEnabled(True) def et(self): '''Change l'état des boutons et ajoute "and" à la requête''' self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnId.setEnabled(True) self.btnRiviere.setEnabled(True) self.btnProprio.setEnabled(True) self.btnAdresse.setEnabled(True) if self.possessionBool == False: self.btnPossession.setEnabled(True) if self.aappmaBool == False: self.btnAappma.setEnabled(True) if self.anneeSignBool == False: self.btnSign.setEnabled(True) if self.anneeFinBool == False: self.btnFin.setEnabled(True) if self.CBool == False: self.btnC.setEnabled(True) if self.CSBool == False: self.btnCS.setEnabled(True) if self.CSPBool == False: self.btnCSP.setEnabled(True) self.wwhere += " AND " def ou(self): '''Change l'état des boutons et ajoute "or" à la requête''' self.btnEt.setEnabled(False) self.btnOu.setEnabled(False) self.btnId.setEnabled(True) self.btnSign.setEnabled(True) self.btnFin.setEnabled(True) self.btnRiviere.setEnabled(True) self.btnAappma.setEnabled(True) self.btnC.setEnabled(True) self.btnCS.setEnabled(True) self.btnCSP.setEnabled(True) self.btnProprio.setEnabled(True) self.btnAdresse.setEnabled(True) self.btnPossession.setEnabled(True) self.possessionBool = False self.aappmaBool = False self.anneeSignBool = False self.anneeFinBool = False self.CBool = False self.CSBool = False self.CSPBool = False self.wwhere += " OR " def ajoutId(self): '''Change l'état des boutons et ajoute un critère d'id à la requête''' self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.wid = self.spnId.value() if self.spnId.value() != "": if self.wid != "": self.wwhere += "bope_id = '" + str(self.wid) + "'" self.spnId.setValue(0) self.spnId.setFocus() def ajoutRiviere(self): '''Change l'état des boutons et ajoute un critère de cours d'eau à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) wfrombce = "bail_cours_eau" if self.dbType == "postgres": self.wfromCeau = self.dbSchema + "." + wfrombce wrecord = self.cmbRiviere.model().record( self.cmbRiviere.currentIndex()) self.wCeau = wrecord.value(0) if self.cmbRiviere.currentText() != "": if self.wCeau != "": self.wwhere += "bope_id in (select distinct bce_bope_id from " + self.wfromCeau + " where bce_ceau_id = '" + str( self.wCeau) + "')" def ajoutAappma(self): '''Change l'état des boutons et ajoute un critère d'aappma à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.aappmaBool = True wrecord = self.cmbAappma.model().record(self.cmbAappma.currentIndex()) self.wbope_aappma = wrecord.value(0) if self.cmbAappma.currentText() != "": if self.wbope_aappma != "": self.wwhere += "bope_apma_id = '" + str( self.wbope_aappma) + "'" def ajoutPossession(self): '''Change l'état des boutons et ajoute un critère de possession à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnSign.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.possessionBool = True if self.chkPossession.isChecked() == True: self.wwherePossession = "bope_existe = True" else: self.wwherePossession = "bope_existe = False" self.wwhere += self.wwherePossession def ajoutDateSign(self): '''Change l'état des boutons et ajoute un critère de date de signature à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnSign.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.anneeSignBool = True self.wbope_date_sign = self.dateSign.date().toString("yyyy") if self.wbope_date_sign != "": self.wwhere += "date_part('year', bope_date_sign) = '" + str( self.wbope_date_sign) + "'" def ajoutDateFin(self): '''Change l'état des boutons et ajoute un critère de date d'expiration à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.anneeFinBool = True self.wbope_date_fin = self.dateFin.date().toString("yyyy") if self.wbope_date_fin != "": self.wwhere += "date_part('year', bope_date_fin) = '" + str( self.wbope_date_fin) + "'" def ajoutCommune(self): '''Change l'état des boutons et ajoute un critère de commune à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.CBool = True wrecord = self.cmbCommune.model().record( self.cmbCommune.currentIndex()) self.wcommune = wrecord.value(0) if self.cmbCommune.currentText() != "": if self.wcommune != "": self.wwhere += "bope_id in (select bope_id from data.droit_peche, data.parcelle, data.section where bope_id = par_bope_id and par_sec_id = sec_id and sec_com_insee = '" + str( self.wcommune) + "')" def ajoutComSection(self): '''Change l'état des boutons et ajoute un critère de commune et section à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.CSBool = True wrecord = self.cmbSection.model().record( self.cmbSection.currentIndex()) self.wsection = wrecord.value(0) if self.cmbSection.currentText() != "": if self.wsection != "": self.wwhere += "bope_id in (select bope_id from data.droit_peche, data.parcelle where par_bope_id = bope_id and par_sec_id = '" + str( self.wsection) + "')" def ajoutComSecParcelle(self): '''Change l'état des boutons et ajoute un critère de commune, section et parcelle à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.CSPBool = True wrecord = self.cmbParcelle.model().record( self.cmbParcelle.currentIndex()) self.wparcelle = wrecord.value(0) if self.cmbParcelle.currentText() != "": if self.wparcelle != "": self.wwhere += "bope_id in (select par_bope_id from data.parcelle where par_id = '" + str( self.wparcelle) + "')" def ajoutProprio(self): ''' Change l'état des boutons et ajoute un critère de nom de propriétaire et / ou de mail et / ou de téléphone à la requête ''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.wwhereProprio = "" self.wnom = self.leNom.text() if "'" in self.wnom and "''" not in self.wnom: self.wnom = self.wnom.replace("'", "''") self.wmail = self.leMail.text() self.wtel = self.leTel.text() if self.leNom.text() != "": if self.wnom != "": self.wwhereProprio += " bope_id in (select bope_id from data.droit_peche, data.proprietaire where bope_pro_id = pro_id and pro_nom ilike '%" + self.wnom + "%')" if self.leMail.text() != "": if self.wmail != "": if self.wnom != "": self.wwhereProprio += " and " self.wwhereProprio += " bope_id in (select bope_id from data.droit_peche, data.proprietaire where bope_pro_id = pro_id and pro_mail = '" + self.wmail + "')" if self.leTel.text() != "": if self.wtel != "": if self.wnom != "" or self.wmail != "": self.wwhereProprio += " and " self.wwhereProprio += " bope_id in (select bope_id from data.droit_peche, data.proprietaire where bope_pro_id = pro_id and pro_telephone = '" + str( self.wtel) + "')" if self.wwhereProprio != "": self.wwhere += self.wwhereProprio self.leNom.setText("") self.leMail.setText("") self.leTel.setText("") self.leNom.setFocus() def ajoutAdresse(self): '''Change l'état des boutons et ajoute un critère d'adresse à la requête''' self.btnEt.setEnabled(True) self.btnOu.setEnabled(True) self.btnId.setEnabled(False) self.btnSign.setEnabled(False) self.btnFin.setEnabled(False) self.btnRiviere.setEnabled(False) self.btnAappma.setEnabled(False) self.btnPossession.setEnabled(False) self.btnC.setEnabled(False) self.btnCS.setEnabled(False) self.btnCSP.setEnabled(False) self.btnProprio.setEnabled(False) self.btnAdresse.setEnabled(False) self.wadresse = self.leAdresse.text() if "'" in self.wadresse and "''" not in self.wadresse: self.wadresse = self.wadresse.replace("'", "''") if self.leAdresse.text() != "": if self.wadresse != "": self.wwhere += " bope_id in (select distinct bope_id from data.droit_peche, data.proprietaire where (bope_pro_id = pro_id ) and (pro_adresse ilike '%" + self.wadresse + "%'))" else: if self.leAdresse.text() == "": if self.wadresse == "": self.wwhere += " bope_id in (select distinct bope_id from data.droit_peche, data.proprietaire where (bope_pro_id = pro_id )" self.leAdresse.setText("") self.leAdresse.setFocus() def creaRequete(self): # def previSql(self): '''Regroupe les différentes variables contenant les clauses de la requête SQL et les concatène pour en faire une requête exécutable''' self.wrq = "" # Construit la clause FROM de la requête cfrom = "droit_peche" if self.dbType == "postgres": cfrom = self.dbSchema + "." + cfrom # Construit la clause SELECT et ajoute la clause FROM à la requête self.wrq = "SELECT DISTINCT bope_id FROM " + cfrom # Construit la clause WHERE et ORDER BY et l'ajoute à la requête if self.wwhere != "": #Supprime l'opérateur "and" ou "or" si celui-ci n'est pas suivi d'un critère operateurs = ["AND", "OR"] fin_where = self.wwhere[-5:] for ext in operateurs: if ext in fin_where: self.wwhere = self.wwhere[:-4] self.wrq += " WHERE " + self.wwhere + " ORDER BY bope_id" else: self.wrq += " ORDER BY bope_id" def previSql(self): '''Permet de prévisualiser la requête avant de l'éxecuter''' self.txtSql.setText("") self.creaRequete() # Affiche la requête self.txtSql.setText(self.wrq) def execution(self): '''Permet d'éxecuter la requête''' # Vérifie la non présence de mot pouvant endommager la base de données erreur = False interdit = [ "update", "delete", "insert", "intersect", "duplicate", "merge", "truncate", "create", "drop", "alter" ] if self.txtSql.toPlainText() != "": self.requete = self.txtSql.toPlainText() else: self.creaRequete() self.requete = self.wrq testRequete = self.requete.lower() for mot in interdit: if mot in testRequete: erreur = True if erreur == True: QMessageBox.critical( self, u"Erreur SQL", u"Vous essayez d'exécuter une requête qui peut endommager la base de données !", QMessageBox.Ok) # Après récupération du contenu de la zone de texte, exécute la requête else: query = QSqlQuery(self.db) query.prepare(self.requete) if query.exec_(): wparam = "" while query.next(): wparam += str(query.value(0)) + "," if (wparam != ""): wparam = "(" + wparam[0:len(wparam) - 1] + ")" if self.modelBauxPe: # Filtre le modèle des droits de pêche et ferme la fenêtre self.modelBauxPe.setFilter("bope_id in %s" % wparam) self.modelBauxPe.select() QDialog.accept(self) else: QMessageBox.information( self, "Filtrage", u"Aucun bail de pêche ne correspond aux critères ...", QMessageBox.Ok) else: QMessageBox.critical(self, u"Erreur SQL", query.lastError().text(), QMessageBox.Ok)
class CTDIVolTab(QDialog): def __init__(self, ctx, *args, **kwargs): super(CTDIVolTab, self).__init__(*args, **kwargs) self.ctx = ctx self.prev_mode = 0 self.method = 0 self.calc_3d_method = 'slice step' self.dcm_3d_method = 'slice step' self.all_slices = False self.all_slices_dcm = False self.all_slices_man = False self.show_graph_ctd = False self.show_graph_tcm = False self.show_graph_ctd_dcm = False self.show_graph_tcm_dcm = False self.adjust_tcm = False self.initVar() self.initModel() self.initUI() self.setModel() self.sigConnect() def initVar(self): self.CTDI = 0 self.DLP = 0 self.tube_current = 100 self.rotation_time = 1 self.pitch = 1 self.coll = 0 self.scan_length = 10 self.mAs = 0 self.eff_mAs = 0 self.CTDIw = 0 self.CTDIv = 0 self.idxs = [] self.currents = [] self.ctdivs = [] self.disable_warning = False def initModel(self): self.brand_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.scanner_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.volt_query = QSqlTableModel(db=self.ctx.database.ctdi_db) self.coll_query = QSqlTableModel(db=self.ctx.database.ctdi_db) # fill brand combobox self.brand_query.setTable("BRAND") self.brand_query.select() self.brand_id = self.brand_query.record(0).value("ID") # fill scanner combobox self.scanner_query.setTable("SCANNER") self.scanner_query.setFilter("BRAND_ID=1") self.scanner_query.select() self.scanner_id = self.scanner_query.record(0).value("ID") # fill voltage combobox self.volt_query.setTable("CTDI_DATA") self.volt_query.setFilter("SCANNER_ID=1") self.volt_query.select() self.CTDI = self.volt_query.record(0).value("CTDI_HEAD") # fill collimation combobox self.coll_query.setTable("COLLIMATION_DATA") self.coll_query.setFilter("SCANNER_ID=1") self.coll_query.select() self.coll = self.coll_query.record(0).value("VALUE") def setModel(self): self.brand_cb.setModel(self.brand_query) self.brand_cb.setModelColumn(self.brand_query.fieldIndex("NAME")) self.scanner_cb.setModel(self.scanner_query) self.scanner_cb.setModelColumn(self.scanner_query.fieldIndex("NAME")) self.volt_cb.setModel(self.volt_query) self.volt_cb.setModelColumn(self.volt_query.fieldIndex("VOLTAGE")) self.coll_cb.setModel(self.coll_query) self.coll_cb.setModelColumn(self.coll_query.fieldIndex("COL_OPTS")) self.on_brand_changed(0) self.brand_items = [ self.brand_cb.itemText(i).lower() for i in range(self.brand_cb.count()) ] def sigConnect(self): self.opts.activated[int].connect(self.on_set_method) self.brand_cb.activated[int].connect(self.on_brand_changed) self.scanner_cb.activated[int].connect(self.on_scanner_changed) self.volt_cb.activated[int].connect(self.on_volt_changed) self.coll_cb.activated[int].connect(self.on_coll_changed) self.tube_current_edit.textChanged[str].connect( self.on_tube_current_changed) self.rotation_time_edit.textChanged[str].connect( self.on_rotation_time_changed) self.pitch_edit.textChanged[str].connect(self.on_pitch_changed) self.scan_length_c_edit.textChanged[str].connect( self.on_scan_length_changed) self.ctdiv_m_edit.textChanged[str].connect(self.on_ctdiv_changed) self.dlp_m_edit.textChanged[str].connect(self.on_dlp_changed) self.tcm_btn.clicked.connect(self.on_get_tcm) self.scn_btn.clicked.connect(self.get_scan_length_dicom) self.get_info_btn.clicked.connect(self.on_get_info) self.calc_btn.clicked.connect(self.calculate_method) self.calc_dcm_btn.clicked.connect(self.calculate_dcm) self.calc_man_btn.clicked.connect(self.calculate_man) self.ctx.app_data.imgChanged.connect(self.img_changed_handle) self.ctx.app_data.imgLoaded.connect(self.img_loaded_handle) self.show_graph_ctd_chk.stateChanged.connect( self.on_show_graph_ctd_state) self.show_graph_tcm_chk.stateChanged.connect( self.on_show_graph_tcm_state) self.show_graph_ctd_dcm_chk.stateChanged.connect( self.on_show_graph_ctd_dcm_state) self.show_graph_tcm_dcm_chk.stateChanged.connect( self.on_show_graph_tcm_dcm_state) self.tcm_correction_chk.stateChanged.connect( self.on_tcm_correction_state) self.all_slices_man_chk.stateChanged.connect(self.on_mode_man_changed) self.mode_calc_cb.activated[int].connect(self.on_mode_calc_changed) self.mode_dcm_cb.activated[int].connect(self.on_mode_dcm_changed) [ btn.toggled.connect(self.on_calc_3d_opts_changed) for btn in self.calc_d3opts_rbtns ] [ btn.toggled.connect(self.on_dcm_3d_opts_changed) for btn in self.dcm_d3opts_rbtns ] def initUI(self): self.figure = PlotDialog() self.opts = QComboBox() self.opts.addItems(['Calculation', 'Get from DICOM', 'Input Manually']) self.brand_cb = QComboBox() self.scanner_cb = QComboBox() self.volt_cb = QComboBox() self.coll_cb = QComboBox() self.mode_calc_cb = QComboBox(self) self.mode_dcm_cb = QComboBox(self) self.mode_calc_cb.addItems(['One slice', 'Z-axis']) self.mode_dcm_cb.addItems(['One slice', 'Z-axis']) self.tube_current_edit = QLineEdit(f'{self.tube_current}') self.rotation_time_edit = QLineEdit(f'{self.rotation_time}') self.pitch_edit = QLineEdit(f'{self.pitch}') self.scan_length_c_edit = QLineEdit(f'{self.scan_length}') self.scan_length_d_edit = QLineEdit(f'{self.scan_length}') self.mas_edit = QLineEdit('0') self.mas_eff_edit = QLineEdit('0') self.ctdiw_edit = QLineEdit('0') self.ctdiv_c_edit = QLineEdit('0') self.ctdiv_m_edit = QLineEdit('0') self.ctdiv_d_edit = QLineEdit('0') self.dlp_c_edit = QLineEdit('0') self.dlp_m_edit = QLineEdit('0') self.dlp_d_edit = QLineEdit('0') self.ctdiv_d_label = QLabel('CTDI<sub>vol</sub> (mGy)') # self.ctdiv_d2_label = QLabel('CTDI<sub>vol</sub> (mGy)') self.get_info_btn = QPushButton('Get Info') self.calc_btn = QPushButton('Calculate') self.calc_dcm_btn = QPushButton('Calculate') self.calc_man_btn = QPushButton('Input') self.tcm_btn = QPushButton('TCM') self.scn_btn = QPushButton('Scn Len') self.next_tab_btn = QPushButton('Next') self.prev_tab_btn = QPushButton('Previous') self.prev_tab_btn.setVisible(False) self.show_graph_tcm_chk = QCheckBox('Show mA Graph') self.show_graph_ctd_chk = QCheckBox('Show CTDIvol Graph') self.show_graph_tcm_dcm_chk = QCheckBox('Show mA Graph') self.show_graph_ctd_dcm_chk = QCheckBox('Show CTDIvol Graph') self.tcm_correction_chk = QCheckBox('Adjust CTDIvol with mA') self.all_slices_man_chk = QCheckBox('All Slices') self.calc_slice1_sb = QSpinBox() self.calc_slice2_sb = QSpinBox() self.calc_to_lbl = QLabel('to') self.calc_d3opts_rbtns = [ QRadioButton('Slice Step'), QRadioButton('Slice Number'), QRadioButton('Regional') ] self.calc_d3opts_rbtns[0].setChecked(True) self.dcm_slice1_sb = QSpinBox() self.dcm_slice2_sb = QSpinBox() self.dcm_to_lbl = QLabel('to') self.dcm_d3opts_rbtns = [ QRadioButton('Slice Step'), QRadioButton('Slice Number'), QRadioButton('Regional') ] self.dcm_d3opts_rbtns[0].setChecked(True) self.calc_slice1_sb.setMaximum(self.ctx.total_img) self.calc_slice1_sb.setMinimum(1) self.calc_slice1_sb.setMinimumWidth(50) self.calc_slice2_sb.setMaximum(self.ctx.total_img) self.calc_slice2_sb.setMinimum(1) self.calc_slice2_sb.setMinimumWidth(50) self.calc_to_lbl.setHidden(True) self.calc_slice2_sb.setHidden(True) self.dcm_slice1_sb.setMaximum(self.ctx.total_img) self.dcm_slice1_sb.setMinimum(1) self.dcm_slice1_sb.setMinimumWidth(50) self.dcm_slice2_sb.setMaximum(self.ctx.total_img) self.dcm_slice2_sb.setMinimum(1) self.dcm_slice2_sb.setMinimumWidth(50) self.dcm_to_lbl.setHidden(True) self.dcm_slice2_sb.setHidden(True) self.brand_cb.setPlaceholderText('[Unavailable]') self.scanner_cb.setPlaceholderText('[Unavailable]') self.volt_cb.setPlaceholderText('[Unavailable]') self.coll_cb.setPlaceholderText('[Unavailable]') cbs = [ self.brand_cb, self.scanner_cb, self.volt_cb, self.coll_cb, self.mode_calc_cb, self.mode_dcm_cb ] edits = [ self.tube_current_edit, self.rotation_time_edit, self.pitch_edit, self.scan_length_c_edit, self.scan_length_d_edit, self.mas_edit, self.mas_eff_edit, self.ctdiw_edit, self.ctdiv_c_edit, self.ctdiv_m_edit, self.ctdiv_d_edit, self.dlp_c_edit, self.dlp_m_edit, self.dlp_d_edit, ] [edit.setValidator(QDoubleValidator()) for edit in edits] [edit.setMinimumWidth(100) for edit in edits] [edit.setMinimumWidth(100) for edit in cbs] [edit.setAlignment(Qt.AlignRight) for edit in edits] self.mas_edit.setReadOnly(True) self.mas_eff_edit.setReadOnly(True) self.ctdiw_edit.setReadOnly(True) self.ctdiv_c_edit.setReadOnly(True) self.dlp_c_edit.setReadOnly(True) self.switch_button_default() self.set_layout() def set_layout(self): font = QFont() font.setBold(True) self.ctdiv_c_edit.setFont(font) self.dlp_c_edit.setFont(font) manual_grpbox = QGroupBox('') manual_layout = QFormLayout() manual_layout.addRow(QLabel('CTDI<sub>vol</sub> (mGy)'), self.ctdiv_m_edit) manual_layout.addRow(QLabel('DLP (mGy-cm))'), self.dlp_m_edit) manual_layout.addRow(self.calc_man_btn, QLabel('')) manual_layout.addRow(self.all_slices_man_chk, QLabel('')) manual_grpbox.setLayout(manual_layout) manual_grpbox.setFont(font) dcm_slice_layout = QHBoxLayout() dcm_slice_layout.addWidget(self.dcm_slice1_sb) dcm_slice_layout.addWidget(self.dcm_to_lbl) dcm_slice_layout.addWidget(self.dcm_slice2_sb) dcm_slice_layout.addStretch() self.dcm_d3opts_grpbox = QGroupBox('Z-axis Options') dcm_d3opts_layout = QVBoxLayout() [dcm_d3opts_layout.addWidget(btn) for btn in self.dcm_d3opts_rbtns] dcm_d3opts_layout.addLayout(dcm_slice_layout) dcm_d3opts_layout.addWidget(self.show_graph_ctd_dcm_chk) dcm_d3opts_layout.addWidget(self.show_graph_tcm_dcm_chk) dcm_d3opts_layout.addStretch() self.dcm_d3opts_grpbox.setLayout(dcm_d3opts_layout) self.dcm_d3opts_grpbox.setVisible(False) dicom_grpbox = QGroupBox('Parameter') dicom_layout = QFormLayout() dicom_layout.addRow(QLabel('Scan Length (cm)'), self.scan_length_d_edit) dicom_layout.addRow(self.ctdiv_d_label, self.ctdiv_d_edit) dicom_layout.addRow(QLabel('DLP (mGy-cm)'), self.dlp_d_edit) # dicom_layout.addRow(QLabel('Option'), self.mode_dcm_cb) dicom_layout.addRow(self.calc_dcm_btn, self.tcm_correction_chk) dicom_grpbox.setLayout(dicom_layout) # dicom_grpbox.setFont(font) dcm_widget = QWidget(self) dcm_layout = QHBoxLayout() dcm_layout.addWidget(dicom_grpbox) dcm_layout.addWidget(self.dcm_d3opts_grpbox) dcm_layout.setContentsMargins(0, 0, 0, 0) dcm_widget.setLayout(dcm_layout) calc_btn_layout = QHBoxLayout() calc_btn_layout.addWidget(self.calc_btn) calc_btn_layout.addWidget(self.get_info_btn) calci_grpbox = QGroupBox('Parameter') calci_outer_layout = QVBoxLayout() calci_inner_layout = QFormLayout() calci_inner_layout.addRow(QLabel('Manufacturer'), self.brand_cb) calci_inner_layout.addRow(QLabel('Scanner'), self.scanner_cb) calci_inner_layout.addRow(QLabel('Voltage (kV)'), self.volt_cb) calci_inner_layout.addRow(QLabel('Tube Current (mA)'), self.tube_current_edit) calci_inner_layout.addRow(QLabel('Rotation Time (s)'), self.rotation_time_edit) calci_inner_layout.addRow(QLabel('Pitch'), self.pitch_edit) calci_inner_layout.addRow(QLabel('Collimation (mm)'), self.coll_cb) calci_inner_layout.addRow(QLabel('Scan Length (cm)'), self.scan_length_c_edit) # calci_inner_layout.addRow(QLabel('Option'), self.mode_calc_cb) calci_outer_layout.addLayout(calci_inner_layout) calci_outer_layout.addSpacerItem(QSpacerItem(1, 10)) calci_outer_layout.addLayout(calc_btn_layout) calci_outer_layout.addStretch() calci_grpbox.setLayout(calci_outer_layout) calco_grpbox = QGroupBox('Output') calco_layout = QFormLayout() calco_layout.addRow(QLabel('mAs'), self.mas_edit) calco_layout.addRow(QLabel('Effective mAs'), self.mas_eff_edit) calco_layout.addRow(QLabel('CTDI<sub>w</sub> (mGy)'), self.ctdiw_edit) calco_layout.addRow(QLabel('<b>CTDI<sub>vol</sub> (mGy)</b>'), self.ctdiv_c_edit) calco_layout.addRow(QLabel('<b>DLP (mGy-cm)</b>'), self.dlp_c_edit) calco_layout.setContentsMargins(11, 3, 11, 3) calco_layout.setVerticalSpacing(5) calco_grpbox.setLayout(calco_layout) # print(calco_layout.verticalSpacing()) calc_slice_layout = QHBoxLayout() calc_slice_layout.addWidget(self.calc_slice1_sb) calc_slice_layout.addWidget(self.calc_to_lbl) calc_slice_layout.addWidget(self.calc_slice2_sb) calc_slice_layout.addStretch() calc_chk_layout = QVBoxLayout() calc_chk_layout.addWidget(self.show_graph_ctd_chk) calc_chk_layout.addWidget(self.show_graph_tcm_chk) calc_chk_layout.setSpacing(3) calc_radio_layout = QVBoxLayout() [calc_radio_layout.addWidget(btn) for btn in self.calc_d3opts_rbtns] calc_radio_layout.setSpacing(3) self.calc_d3opts_grpbox = QGroupBox('Z-axis Options') calc_d3opts_layout = QVBoxLayout() calc_d3opts_layout.addLayout(calc_radio_layout) calc_d3opts_layout.addLayout(calc_slice_layout) calc_d3opts_layout.addLayout(calc_chk_layout) calc_d3opts_layout.addStretch() calc_d3opts_layout.setContentsMargins(11, 3, 11, 3) calc_d3opts_layout.setSpacing(0) self.calc_d3opts_grpbox.setLayout(calc_d3opts_layout) self.calc_d3opts_grpbox.setVisible(False) calcr_layout = QVBoxLayout() calcr_layout.addWidget(calco_grpbox) calcr_layout.addWidget(self.calc_d3opts_grpbox) calcr_layout.setSpacing(0) # calcr_layout.addWidget(self.calc_graph_opts_grpbox) calcr_layout.addStretch() calc_widget = QWidget(self) calc_layout = QHBoxLayout() calc_layout.addWidget(calci_grpbox) calc_layout.addLayout(calcr_layout) calc_layout.setContentsMargins(0, 0, 0, 0) calc_widget.setLayout(calc_layout) self.stacks = QStackedLayout() self.stacks.addWidget(calc_widget) self.stacks.addWidget(dcm_widget) self.stacks.addWidget(manual_grpbox) self.emp = QLabel('') self.stackz = QStackedLayout() self.stackz.addWidget(self.mode_calc_cb) self.stackz.addWidget(self.mode_dcm_cb) self.stackz.addWidget(self.emp) self.emp.setVisible(False) tab_nav = QHBoxLayout() tab_nav.addWidget(self.prev_tab_btn) tab_nav.addStretch() tab_nav.addWidget(self.next_tab_btn) main_layout = QVBoxLayout() main_layout.addWidget(QLabel('Methods:')) main_layout.addWidget(self.opts) main_layout.addLayout(self.stackz) main_layout.addStretch() main_layout.addLayout(self.stacks) main_layout.addStretch() main_layout.addLayout(tab_nav) self.setLayout(main_layout) def switch_button_default(self, method=0): if method == 0: if self.method == 0: self.calc_btn.setAutoDefault(True) self.calc_btn.setDefault(True) self.calc_dcm_btn.setAutoDefault(False) self.calc_dcm_btn.setDefault(False) elif self.method == 1: self.calc_dcm_btn.setAutoDefault(True) self.calc_dcm_btn.setDefault(True) self.calc_btn.setAutoDefault(False) self.calc_btn.setDefault(False) self.next_tab_btn.setAutoDefault(False) self.next_tab_btn.setDefault(False) elif method == 1: self.next_tab_btn.setAutoDefault(True) self.next_tab_btn.setDefault(True) self.calc_btn.setAutoDefault(False) self.calc_btn.setDefault(False) self.calc_dcm_btn.setAutoDefault(False) self.calc_dcm_btn.setDefault(False) else: return self.prev_tab_btn.setAutoDefault(False) self.prev_tab_btn.setDefault(False) self.tcm_btn.setAutoDefault(False) self.tcm_btn.setDefault(False) self.scn_btn.setAutoDefault(False) self.scn_btn.setDefault(False) self.get_info_btn.setAutoDefault(False) self.get_info_btn.setDefault(False) def plot_ctdiv(self): xlabel = 'CTDIvol' title = 'CTDIvol' self.figure_ctdi = PlotDialog() self.figure_ctdi.actionEnabled(True) self.figure_ctdi.trendActionEnabled(False) self.figure_ctdi.plot(self.idxs, self.ctdivs, pen={ 'color': "FFFF00", 'width': 2 }, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure_ctdi.axes.showGrid(True, True) self.figure_ctdi.setLabels('slice', xlabel, '', 'mGy') self.figure_ctdi.setTitle(f'Slice - {title}') self.figure_ctdi.show() def plot_tcm(self): xlabel = 'Tube Current' title = 'Tube Current' self.figure = PlotDialog() self.figure.actionEnabled(True) self.figure.trendActionEnabled(False) self.figure.plot(self.idxs, self.currents, pen={ 'color': "FFFF00", 'width': 2 }, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure.axes.showGrid(True, True) self.figure.setLabels('slice', xlabel, '', 'mA') self.figure.setTitle(f'Slice - {title}') self.figure.show() def on_get_tcm(self): if not self.ctx.isImage and not self.disable_warning: QMessageBox.warning(None, "Warning", "Open DICOM files first, or input manually") self.opts.setCurrentIndex(0) return self.currents = [] self.idxs = [] try: for idx, dcm in enumerate(self.ctx.dicoms): self.currents.append(float(dcm.XRayTubeCurrent)) self.idxs.append(idx + 1) except Exception as e: self.currents = [] self.idxs = [] if not self.disable_warning: QMessageBox.warning(None, 'Exception Occured', str(e)) return tube_current = sum(self.currents) / self.ctx.total_img self.tube_current_edit.setText(f'{tube_current:#.2f}') self.tube_current = tube_current self.plot_tcm() def get_ctdiv_dicom(self, idx): try: ctdiv = float(self.ctx.dicoms[idx - 1].CTDIvol) except: if not self.disable_warning: QMessageBox.warning( None, "Warning", "The DICOM does not contain the value of CTDIvol.\nPlease try different method." ) ctdiv = 0 return ctdiv def get_ctdiv_dicom_3d(self, idxs, dss): ctdivs = [] currents = [] to_remove = [] for idx, dcm in zip(idxs, dss): if not hasattr(dcm, 'CTDIvol') or not hasattr( dcm, 'XRayTubeCurrent'): to_remove.append(idx) continue ctdivs.append(float(dcm.CTDIvol)) currents.append(float(dcm.XRayTubeCurrent)) if len(to_remove) > 0: [idxs.remove(idx) for idx in to_remove if idx in idxs] self.currents = np.array(currents) self.ctdivs = np.array(ctdivs) return idxs def get_scan_length_dicom(self): if self.method == 0 and not self.ctx.isImage and not self.disable_warning: QMessageBox.warning(None, "Warning", "Open DICOM files first, or input manually") self.opts.setCurrentIndex(0) return try: first = float(self.ctx.dicoms[0].SliceLocation) last = float(self.ctx.dicoms[-1].SliceLocation) width = float(self.ctx.dicoms[0].SliceThickness) try: second = float(self.ctx.dicoms[1].SliceLocation) except: second = last except Exception as e: if not self.disable_warning: QMessageBox.warning(None, 'Exception Occured', str(e)) return lf = abs(0.1 * (last - first)) sf = abs(0.1 * (second - first)) print(lf, sf, width / 10) scan_length = (abs((last - first)) + abs( (second - first)) + width) * .1 self.scan_length = scan_length if self.method == 0: self.scan_length_c_edit.setText(f'{self.scan_length:#.2f}') def on_get_info(self): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first, or input manually") return def find_closest_value(array, number): return min(array, key=lambda x: abs(x - number)) attrs = [ 'Manufacturer', 'ManufacturerModelName', 'KVP', 'XRayTubeCurrent', 'ExposureTime', 'SpiralPitchFactor', 'TotalCollimationWidth' ] kv_pairs = {} missing_data = {} missing_attr = [] for attr in attrs: try: kv_pairs[attr] = self.ctx.dicoms[self.ctx.current_img - 1][attr].value except KeyError: kv_pairs[attr] = 0 missing_attr.append(attr) if kv_pairs[attrs[0]].lower() in self.brand_items: brand_id = self.brand_items.index(kv_pairs[attrs[0]].lower()) self.brand_cb.setCurrentIndex(brand_id) self.on_brand_changed(brand_id) if kv_pairs[attrs[1]].lower() in self.scanner_items: scanner_id = self.scanner_items.index( kv_pairs[attrs[1]].lower()) self.scanner_cb.setCurrentIndex(scanner_id) self.on_scanner_changed(scanner_id) volt_id = self.volt_items.index( find_closest_value(self.volt_items, kv_pairs[attrs[2]])) coll_id = self.coll_items.index( find_closest_value(self.coll_items, kv_pairs[attrs[6]])) self.volt_cb.setCurrentIndex(volt_id) self.on_volt_changed(volt_id) self.coll_cb.setCurrentIndex(coll_id) self.on_coll_changed(coll_id) else: missing_data[attrs[1]] = kv_pairs[attrs[1]] else: missing_data[attrs[0]] = kv_pairs[attrs[0]] self.tube_current_edit.setText(f"{kv_pairs['XRayTubeCurrent']:#.2f}") self.rotation_time_edit.setText( f"{kv_pairs['ExposureTime']/1000:#.2f}") self.pitch_edit.setText(f"{kv_pairs['SpiralPitchFactor']:#.2f}") self.get_scan_length_dicom() if missing_attr: QMessageBox.information( None, 'Missing Attribute', f"The image has no attribute '{', '.join(missing_attr)}'.\nPlease input them manually." ) if missing_data: QMessageBox.information( None, 'No Data', f"Data for '{list(missing_data.keys())[0]}: {list(missing_data.values())[0]}' is unavailable." ) def on_set_method(self, idx): self.disable_warning = False self.prev_mode = self.method self.method = idx self.stacks.setCurrentIndex(idx) self.stackz.setCurrentIndex(idx) if self.method == 2: pass # self.calculate() elif self.method == 0: self.on_mode_calc_changed(self.mode_calc_cb.currentIndex()) elif self.method == 1: self.on_mode_dcm_changed(self.mode_dcm_cb.currentIndex()) def on_brand_changed(self, sel): self.brand_id = self.brand_query.record(sel).value("ID") self.scanner_query.setFilter(f"BRAND_ID={self.brand_id}") self.on_scanner_changed(0) self.scanner_items = [ self.scanner_cb.itemText(i).lower() for i in range(self.scanner_cb.count()) ] def on_scanner_changed(self, sel): self.scanner_id = self.scanner_query.record(sel).value("ID") self.volt_query.setFilter(f"SCANNER_ID={self.scanner_id}") self.coll_query.setFilter(f"SCANNER_ID={self.scanner_id}") if self.volt_cb.count() == 0: QMessageBox.warning(None, 'No Data', f'There is no CTDI data for this scanner.') if self.coll_cb.count() == 0: QMessageBox.warning( None, 'No Data', 'There is no collimation data for this scanner.') self.on_volt_changed(0) self.on_coll_changed(0) self.volt_items = [ float(self.volt_cb.itemText(i)) for i in range(self.volt_cb.count()) ] self.coll_items = [ float(self.coll_query.record(i).value("COL_VAL")) for i in range(self.coll_cb.count()) ] def on_volt_changed(self, sel): phantom = 'head' if self.ctx.phantom == HEAD else 'body' self.CTDI = self.volt_query.record(sel).value( f"CTDI_{phantom.upper()}") if not self.CTDI and self.volt_cb.count() != 0: QMessageBox.warning( None, 'No Data', f'There is no {phantom.capitalize()} CTDI value for this voltage value.' ) # self.calculate(False) def on_coll_changed(self, sel): self.coll = self.coll_query.record(sel).value("VALUE") if not self.coll and self.coll_cb.count() != 0: QMessageBox.warning( None, 'No Data', 'There is no collimation data for this option.') # self.calculate(False) def on_tube_current_changed(self, sel): try: self.tube_current = float(sel) except ValueError: self.tube_current = 0 # self.calculate(False) def on_rotation_time_changed(self, sel): try: self.rotation_time = float(sel) except ValueError: self.rotation_time = 1 # self.calculate(False) def on_pitch_changed(self, sel): try: self.pitch = float(sel) except ValueError: self.pitch = 1 # self.calculate(False) def on_scan_length_changed(self, sel): try: self.scan_length = float(sel) except ValueError: self.scan_length = 0 # self.calculate(False) def on_dlp_changed(self, sel): try: self.DLP = float(sel) except ValueError: self.DLP = 0 self.set_app_data() def on_ctdiv_changed(self, sel): try: self.CTDIv = float(sel) except ValueError: self.CTDIv = 0 self.set_app_data() def on_dicom_manual(self): try: self.CTDIv = float(self.ctdiv_d_edit.text()) except: self.CTDIv = 0 try: self.scan_length = float(self.scan_length_d_edit.text()) except: self.scan_length = 0 self.DLP = self.CTDIv * self.scan_length self.dlp_d_edit.setText(f'{self.DLP:#.2f}') def img_changed_handle(self, value): if value: self.switch_button_default() def img_loaded_handle(self, state): if not state: self.all_slices_man = False self.all_slices_man_chk.setChecked(False) self.all_slices_man_chk.setEnabled(state) def on_mode_man_changed(self, state): self.all_slices_man = state == Qt.Checked self.ctx.app_data.c_mode = int(self.all_slices_man) def on_mode_dcm_changed(self, idx): self.all_slices_dcm = idx == 1 if self.all_slices_dcm: self.ctdiv_d_label.setText("Avg. CTDI<sub>vol</sub> (mGy)") else: self.ctdiv_d_label.setText("CTDI<sub>vol</sub> (mGy)") self.show_graph_tcm_dcm_chk.setCheckState(Qt.Unchecked) self.show_graph_ctd_dcm_chk.setCheckState(Qt.Unchecked) self.dcm_d3opts_grpbox.setVisible(self.all_slices_dcm) self.show_graph_tcm_dcm = False self.show_graph_ctd_dcm = False self.ctx.app_data.c_mode = idx self.reset_dcm() self.mode_calc_cb.setCurrentIndex(idx) def on_mode_calc_changed(self, idx): self.all_slices = idx == 1 self.show_graph_tcm_chk.setCheckState(Qt.Unchecked) self.show_graph_ctd_chk.setCheckState(Qt.Unchecked) self.calc_d3opts_grpbox.setVisible(self.all_slices) self.show_graph_tcm = False self.show_graph_ctd = False self.ctx.app_data.c_mode = idx self.mode_dcm_cb.setCurrentIndex(idx) def on_show_graph_ctd_state(self, state): self.show_graph_ctd = state == Qt.Checked def on_show_graph_tcm_state(self, state): self.show_graph_tcm = state == Qt.Checked def on_show_graph_ctd_dcm_state(self, state): self.show_graph_ctd_dcm = state == Qt.Checked def on_show_graph_tcm_dcm_state(self, state): self.show_graph_tcm_dcm = state == Qt.Checked def on_tcm_correction_state(self, state): self.adjust_tcm = state == Qt.Checked def on_calc_3d_opts_changed(self, sel): sel = self.sender() if sel.isChecked(): self.calc_3d_method = sel.text().lower() if self.calc_3d_method == 'regional': self.calc_to_lbl.setHidden(False) self.calc_slice2_sb.setHidden(False) self.calc_slice1_sb.setMinimum(1) self.calc_slice1_sb.setMaximum(self.ctx.total_img) else: self.calc_to_lbl.setHidden(True) self.calc_slice2_sb.setHidden(True) def on_dcm_3d_opts_changed(self, sel): sel = self.sender() if sel.isChecked(): self.dcm_3d_method = sel.text().lower() if self.dcm_3d_method == 'regional': self.dcm_to_lbl.setHidden(False) self.dcm_slice2_sb.setHidden(False) self.dcm_slice1_sb.setMinimum(1) self.dcm_slice1_sb.setMaximum(self.ctx.total_img) else: self.dcm_to_lbl.setHidden(True) self.dcm_slice2_sb.setHidden(True) def calc_all_slices(self, idxs, dss): if not self.ctx.isImage: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return c = [] e = [] to_remove = [] for idx, dcm in zip(idxs, dss): if not hasattr(dcm, 'XRayTubeCurrent') or not hasattr( dcm, 'ExposureTime'): to_remove.append(idx) continue c.append(float(dcm.XRayTubeCurrent)) e.append(float(dcm.ExposureTime) / 1000) if len(to_remove) > 0: [idxs.remove(idx) for idx in to_remove if idx in idxs] currents = np.array(c) exp_time = np.array(e) mAs = currents * exp_time eff_mAs = mAs / self.pitch if self.pitch > 0 else mAs ctdiw = self.coll * self.CTDI * mAs / 100 ctdiv = ctdiw / self.pitch # print(idx+1, currents[idx], exp_time[idx], mAs[idx], eff_mAs[idx], ctdiw[idx], ctdiv[idx], sep='\t') self.currents = currents self.ctdivs = ctdiv # self.idxs = np.arange(1, self.ctx.total_img+1, dtype=int) self.tube_current_edit.setText(f"{currents.mean():#.2f}") self.rotation_time_edit.setText(f"{exp_time.mean():#.2f}") self.mAs = mAs.mean() self.eff_mAs = eff_mAs.mean() self.CTDIw = ctdiw.mean() self.CTDIv = ctdiv.mean() return idxs def calculate_method(self): if not self.all_slices: self.mAs = self.tube_current * self.rotation_time try: self.eff_mAs = self.mAs / self.pitch except ZeroDivisionError: self.eff_mAs = self.mAs try: self.CTDIw = self.coll * self.CTDI * self.mAs / 100 except TypeError: self.CTDIw = 0 try: # self.CTDIv = self.coll*self.CTDI*self.eff_mAs / 100 self.CTDIv = self.CTDIw / self.pitch except: self.CTDIv = 0 self.ctx.app_data.CTDIvs[self.ctx.current_img] = self.CTDIv self.ctx.app_data.emit_c_changed() else: self.idxs = [] nslice = self.calc_slice1_sb.value() dcms = np.array(self.ctx.dicoms) index = list(range(len(dcms))) self.ctx.app_data.mode3d = self.calc_3d_method self.ctx.app_data.slice1 = nslice if self.calc_3d_method == 'slice step': idxs = index[::nslice] imgs = dcms[::nslice] elif self.calc_3d_method == 'slice number': tmps = np.array_split(np.arange(len(dcms)), nslice) idxs = [tmp[len(tmp) // 2] for tmp in tmps] imgs = dcms[idxs] elif self.calc_3d_method == 'regional': nslice2 = self.calc_slice2_sb.value() self.ctx.app_data.slice2 = nslice2 first = nslice if nslice <= nslice2 else nslice2 last = nslice2 if nslice <= nslice2 else nslice idxs = index[first - 1:last] imgs = dcms[first - 1:last] else: imgs = dcms idxs = index idxs = self.calc_all_slices(idxs, imgs) if idxs is None: return self.idxs = [i + 1 for i in idxs] for k, v in zip(self.idxs, self.ctdivs): self.ctx.app_data.CTDIvs[k] = v self.ctx.app_data.emit_c_changed() self.DLP = self.CTDIv * self.scan_length self.mas_edit.setText(f'{self.mAs:#.2f}') self.mas_eff_edit.setText(f'{self.eff_mAs:#.2f}') self.ctdiw_edit.setText(f'{self.CTDIw:#.2f}') self.ctdiv_c_edit.setText(f'{self.CTDIv:#.2f}') self.dlp_c_edit.setText(f'{self.DLP:#.2f}') self.ctx.app_data.CTDIv = self.CTDIv self.ctx.app_data.DLP = self.DLP if self.show_graph_tcm: self.plot_tcm() if self.show_graph_ctd: self.plot_ctdiv() self.switch_button_default(method=1) def calculate_dcm(self): self.idxs = [] if not self.ctx.isImage and not self.disable_warning: QMessageBox.warning(None, "Warning", "Open DICOM files first.") return self.get_scan_length_dicom() # curCTDIv = self.get_ctdiv_dicom(self.ctx.current_img) dcms = np.array(self.ctx.dicoms) index = list(range(len(dcms))) index = self.get_ctdiv_dicom_3d(index, dcms) if self.all_slices_dcm: nslice = self.dcm_slice1_sb.value() self.ctx.app_data.mode3d = self.dcm_3d_method self.ctx.app_data.slice1 = nslice if self.dcm_3d_method == 'slice step': idxs = index[::nslice] elif self.dcm_3d_method == 'slice number': tmps = np.array_split(np.arange(len(dcms)), nslice) idxs = [tmp[len(tmp) // 2] for tmp in tmps] elif self.dcm_3d_method == 'regional': nslice2 = self.dcm_slice2_sb.value() self.ctx.app_data.slice2 = nslice2 first = nslice if nslice <= nslice2 else nslice2 last = nslice2 if nslice <= nslice2 else nslice idxs = index[first - 1:last] else: idxs = index else: idxs = index if self.adjust_tcm: self.ctdivs = (self.ctdivs * self.currents) / self.currents.mean() self.currents = self.currents[idxs] self.ctdivs = self.ctdivs[idxs] self.CTDIv = self.ctdivs.mean() curidx = idxs.index(self.ctx.current_img - 1) if self.ctx.current_img - 1 in idxs else None if self.all_slices_dcm: ctdi = self.CTDIv else: ctdi = self.ctdivs[curidx] if curidx is not None else 0 scan_length = self.scan_length DLP = ctdi * scan_length self.ctdiv_d_edit.setText(f'{ctdi:#.2f}') self.scan_length_d_edit.setText(f'{scan_length:#.2f}') self.dlp_d_edit.setText(f'{DLP:#.2f}') self.CTDIv = ctdi self.scan_length = scan_length self.DLP = DLP if self.all_slices_dcm: self.idxs = [i + 1 for i in idxs] for k, v in zip(self.idxs, self.ctdivs): self.ctx.app_data.CTDIvs[k] = v else: self.ctx.app_data.CTDIvs[self.ctx.current_img] = ctdi self.ctx.app_data.emit_c_changed() self.ctx.app_data.CTDIv = self.CTDIv self.ctx.app_data.DLP = self.DLP if self.show_graph_ctd_dcm: self.plot_ctdiv() if self.show_graph_tcm_dcm: self.plot_tcm() self.switch_button_default(method=1) def calculate_man(self): try: ctdi = float(self.ctdiv_m_edit.text()) except: ctdi = 0 try: self.DLP = float(self.dlp_m_edit.text()) except: self.DLP = 0 if self.all_slices_man: for idx in range(1, self.ctx.total_img + 1): self.ctx.app_data.CTDIvs[idx] = ctdi else: self.ctx.app_data.CTDIvs[self.ctx.current_img] = ctdi self.ctx.app_data.DLP = self.DLP def calculate(self, auto=True): if self.method == 2: self.ctx.app_data.c_mode = 0 try: self.CTDIv = float(self.ctdiv_m_edit.text()) except: self.CTDIv = 0 try: self.DLP = float(self.dlp_m_edit.text()) except: self.DLP = 0 self.idxs = range(1, self.ctx.total_img + 1) self.ctdivs = [self.CTDIv for _ in self.idxs] self.ctx.app_data.CTDIv = self.CTDIv self.ctx.app_data.DLP = self.DLP self.switch_button_default(method=1) def set_app_data(self): self.ctx.app_data.CTDIv = self.CTDIv self.ctx.app_data.DLP = self.DLP def reset_dcm(self): self.scan_length = 10 self.scan_length_d_edit.setText(f"{self.scan_length}") self.ctdivs = [] self.CTDIv = 0 self.DLP = 0 self.ctdiv_d_edit.setText(f"{self.CTDIv}") self.dlp_d_edit.setText(f"{self.DLP}") def reset_fields(self): self.initVar() self.switch_button_default() self.disable_warning = True self.reset_dcm() self.ctdiv_m_edit.setText('0') self.dlp_m_edit.setText('0') self.tube_current_edit.setText(f'{self.tube_current:#.2f}') self.rotation_time_edit.setText(f'{self.rotation_time:#.2f}') self.pitch_edit.setText(f'{self.pitch:#.2f}') self.scan_length_c_edit.setText(f'{self.scan_length:#.2f}') self.mas_edit.setText(f'{self.mAs:#.2f}') self.mas_eff_edit.setText(f'{self.eff_mAs:#.2f}') self.ctdiw_edit.setText(f'{self.CTDIw:#.2f}') self.ctdiv_c_edit.setText(f'{self.CTDIv:#.2f}') self.dlp_c_edit.setText(f'{self.DLP:#.2f}') self.disable_warning = False
class SSDETab(QDialog): def __init__(self, ctx, *args, **kwargs): super(SSDETab, self).__init__(*args, **kwargs) self.ctx = ctx self.show_graph = False self.show_ssde_graph = False self.all_slices = False self.d3_method = 'slice step' self.current_idx = 0 self.initVar() self.initModel() self.initUI() self.sigConnect() def initVar(self): self.diameter = 0 self.CTDIv = 0 self.convf = 0 self.SSDE = 0 self.DLPc = 0 self.effdose = 0 self.ctdivs = [] self.diameters = [] self.idxs = [] self.ssdes = [] self.display = { 'ctdi': None, 'diameter': None, 'cf': None, 'ssde': None, 'dlp': None, 'dlpc': None, 'effdose': None, } def set_data(self): self.display['ctdi'] = self.CTDIv self.display['diameter'] = self.diameter self.display['cf'] = self.convf self.display['ssde'] = self.SSDE self.display['dlp'] = self.ctx.app_data.DLP self.display['dlpc'] = self.DLPc self.display['effdose'] = self.effdose def initModel(self): self.protocol_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.protocol_model.setTable("Protocol") self.protocol_model.setFilter("Group_ID=1") self.protocol_model.select() self.report_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.report_model.setTable("Report") self.report_model.select() self.cf_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.cf_model.setTable("ConversionFactor") self.cf_model.setFilter("report_id=1") self.cf_model.select() self.effdose_model = QSqlTableModel(db=self.ctx.database.ssde_db) self.effdose_model.setTable("Effective_Dose") self.effdose_model.select() def initUI(self): self.hidden_btn = QPushButton('check') # for debug self.hidden_btn.setVisible(False) self.figure = PlotDialog() self.protocol_cb = QComboBox() self.protocol_cb.setModel(self.protocol_model) self.protocol_cb.setModelColumn(self.protocol_model.fieldIndex('name')) self.report_cb = QComboBox() self.report_cb.setModel(self.report_model) self.report_cb.setModelColumn(self.report_model.fieldIndex('name')) self.plot_chk = QCheckBox('Show Graph') self.show_ssde_graph_chk = QCheckBox('Show SSDE Graph') self.d3_opts_cb = QComboBox() self.d3_opts_cb.addItems(['One slice', 'Z-axis']) self.d3opts_rbtns = [QRadioButton('Slice Step'), QRadioButton('Slice Number'), QRadioButton('Regional')] self.d3opts_rbtns[0].setChecked(True) self.slice1_sb = QSpinBox() self.slice2_sb = QSpinBox() self.to_lbl = QLabel('to') self.slice1_sb.setMaximum(self.ctx.total_img) self.slice1_sb.setMinimum(1) self.slice1_sb.setMinimumWidth(50) self.slice2_sb.setMaximum(self.ctx.total_img) self.slice2_sb.setMinimum(1) self.slice2_sb.setMinimumWidth(50) self.to_lbl.setHidden(True) self.slice2_sb.setHidden(True) self.calc_btn = QPushButton('Calculate') self.save_btn = QPushButton('Save') self.calc_btn.setAutoDefault(True) self.calc_btn.setDefault(True) self.save_btn.setAutoDefault(False) self.save_btn.setDefault(False) self.ctdiv_edit = QLineEdit(f'{self.ctx.app_data.CTDIv}') self.diameter_edit = QLineEdit(f'{self.ctx.app_data.diameter}') self.convf_edit = QLineEdit(f'{self.ctx.app_data.convf}') self.ssde_edit = QLineEdit(f'{self.ctx.app_data.SSDE}') self.dlp_edit = QLineEdit(f'{self.ctx.app_data.DLP}') self.dlpc_edit = QLineEdit(f'{self.ctx.app_data.DLPc}') self.effdose_edit = QLineEdit(f'{self.ctx.app_data.effdose}') self.current_dlabel = '<b>Deff (cm)</b>' self.diameter_label = QLabel(self.current_dlabel) self.ctdiv_label = QLabel('<b>CTDI<sub>vol</sub> (mGy)</b>') self.ssde_label = QLabel('<b>SSDE (mGy)</b>') self.diameter_mode_handle(DEFF_IMAGE) self.next_tab_btn = QPushButton('Next') self.prev_tab_btn = QPushButton('Previous') self.next_tab_btn.setAutoDefault(False) self.next_tab_btn.setDefault(False) self.prev_tab_btn.setAutoDefault(False) self.prev_tab_btn.setDefault(False) self.next_tab_btn.setVisible(False) edits = [ self.ctdiv_edit, self.diameter_edit, self.convf_edit, self.ssde_edit, self.dlp_edit, self.dlpc_edit, self.effdose_edit, ] [edit.setReadOnly(True) for edit in edits] [edit.setAlignment(Qt.AlignRight) for edit in edits] slice_layout = QHBoxLayout() slice_layout.addWidget(self.slice1_sb) slice_layout.addWidget(self.to_lbl) slice_layout.addWidget(self.slice2_sb) slice_layout.addStretch() self.d3opts_grpbox = QGroupBox('Z-axis Options') dcm_d3opts_layout = QVBoxLayout() [dcm_d3opts_layout.addWidget(btn) for btn in self.d3opts_rbtns] dcm_d3opts_layout.addLayout(slice_layout) dcm_d3opts_layout.addWidget(self.show_ssde_graph_chk) dcm_d3opts_layout.addStretch() dcm_d3opts_layout.setContentsMargins(11,3,11,3) self.d3opts_grpbox.setLayout(dcm_d3opts_layout) self.d3opts_grpbox.setVisible(False) left_grpbox = QGroupBox() left_layout = QFormLayout() left_layout.addRow(self.ctdiv_label, self.ctdiv_edit) left_layout.addRow(self.diameter_label, self.diameter_edit) left_layout.addRow(QLabel('<b>Conv Factor</b>'), self.convf_edit) left_layout.addRow(self.ssde_label, self.ssde_edit) # left_layout.addRow(QLabel('<b>Option</b>'), self.d3_opts_cb) left_layout.addRow(self.calc_btn, self.plot_chk) left_layout.addRow(QLabel('')) # left_layout.addRow(QLabel(''), self.plot_chk) left_layout.addRow(self.save_btn, QLabel('')) left_grpbox.setLayout(left_layout) right_grpbox = QGroupBox() right_layout = QFormLayout() right_layout.addRow(QLabel('<b>DLP (mGy-cm)</b>'), self.dlp_edit) right_layout.addRow(QLabel('<b>DLP<sub>c</sub> (mGy-cm)</b>'), self.dlpc_edit) right_layout.addRow(QLabel('<b>Effective Dose (mSv)</b>'), self.effdose_edit) right_grpbox.setLayout(right_layout) r_layout = QVBoxLayout() r_layout.addWidget(right_grpbox) r_layout.addWidget(self.d3opts_grpbox) h = QHBoxLayout() h.addWidget(left_grpbox) h.addLayout(r_layout) tab_nav = QHBoxLayout() tab_nav.addWidget(self.prev_tab_btn) tab_nav.addWidget(self.hidden_btn) tab_nav.addStretch() tab_nav.addWidget(self.next_tab_btn) main_layout = QVBoxLayout() main_layout.addWidget(QLabel('Based on:')) main_layout.addWidget(self.report_cb) main_layout.addWidget(QLabel('Protocol:')) main_layout.addWidget(self.protocol_cb) main_layout.addWidget(HSeparator()) main_layout.addWidget(self.d3_opts_cb) main_layout.addLayout(h) main_layout.addStretch() main_layout.addLayout(tab_nav) self.setLayout(main_layout) def sigConnect(self): self.hidden_btn.clicked.connect(self.on_check) self.protocol_cb.activated[int].connect(self.on_protocol_changed) self.report_cb.activated[int].connect(self.on_report_changed) self.calc_btn.clicked.connect(self.on_calculate) self.ctx.app_data.modeValueChanged.connect(self.diameter_mode_handle) self.ctx.app_data.diametersUpdated.connect(self.update_values) self.ctx.app_data.ctdivsUpdated.connect(self.update_values) self.ctx.app_data.DLPValueChanged.connect(self.dlp_handle) self.ctx.app_data.imgChanged.connect(self.img_changed_handle) self.ctx.app_data.sliceOptChanged.connect(self.sliceopt_handle) self.ctx.app_data.mode3dChanged.connect(self.mode3d_changed_handle) self.ctx.app_data.slice1Changed.connect(self.slice1_changed_handle) self.ctx.app_data.slice2Changed.connect(self.slice2_changed_handle) self.plot_chk.stateChanged.connect(self.on_show_graph_check) self.show_ssde_graph_chk.stateChanged.connect(self.on_show_ssde_graph_check) self.d3_opts_cb.activated[int].connect(self.on_mode_changed) [btn.toggled.connect(self.on_3d_opts_changed) for btn in self.d3opts_rbtns] def plot(self, data): x = self.diameter y = self.convf xlabel = 'Dw' if self.ctx.app_data.mode==DW else 'Deff' title = 'Water Equivalent Diameter' if self.ctx.app_data.mode==DW else 'Effective Diameter' self.figure = PlotDialog() self.figure.actionEnabled(True) self.figure.trendActionEnabled(False) self.figure.plot(data, pen={'color': "FFFF00", 'width': 2}, symbol=None) self.figure.plot([x], [y], symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure.annotate('cf', pos=(x,y), text=f'{xlabel}: {x:#.2f} cm\nConv. Factor: {y:#.2f}', anchor=(0,1)) self.figure.axes.showGrid(True,True) self.figure.setLabels(xlabel,'Conversion Factor','cm','') self.figure.setTitle(f'{title} - Conversion Factor') self.figure.show() def plot_ssde(self, idxs, ssdes): xlabel = 'SSDE' title = 'SSDE' self.figure_ssde = PlotDialog() self.figure_ssde.actionEnabled(True) self.figure_ssde.trendActionEnabled(False) self.figure_ssde.plot(idxs, ssdes, pen={'color': "FFFF00", 'width': 2}, symbol='o', symbolPen=None, symbolSize=8, symbolBrush=(255, 0, 0, 255)) self.figure_ssde.axes.showGrid(True,True) self.figure_ssde.setLabels('slice',xlabel,'','mGy') self.figure_ssde.setTitle(f'Slice - {title}') self.figure_ssde.show() def switch_button_default(self, mode=0): if mode==0: self.calc_btn.setAutoDefault(True) self.calc_btn.setDefault(True) self.save_btn.setAutoDefault(False) self.save_btn.setDefault(False) elif mode==1: self.save_btn.setAutoDefault(True) self.save_btn.setDefault(True) self.calc_btn.setAutoDefault(False) self.calc_btn.setDefault(False) else: return self.next_tab_btn.setAutoDefault(False) self.next_tab_btn.setDefault(False) self.prev_tab_btn.setAutoDefault(False) self.prev_tab_btn.setDefault(False) def diameter_mode_handle(self, value): self.current_dlabel = '<b>Dw (cm)</b>' if value == DW else '<b>Deff (cm)</b>' self.diameter_label.setText(self.current_dlabel) def diameter_handle(self, value): self.diameter_edit.setText(f'{value:#.2f}') def ctdiv_handle(self, value): self.ctdiv_edit.setText(f'{value:#.2f}') def dlp_handle(self, value): self.dlp_edit.setText(f'{value:#.2f}') def on_protocol_changed(self, idx): self.protocol_id = self.protocol_model.record(idx).value("id") self.alfa = self.effdose_model.record(self.protocol_id-1).value("alfaE") self.beta = self.effdose_model.record(self.protocol_id-1).value("betaE") def on_report_changed(self, idx): self.report_id = self.report_model.record(idx).value("id") self.cf_model.setFilter(f"report_id={self.report_id} AND phantom_id={self.ctx.phantom}") self.cf_model.select() a_val = self.cf_model.record(0).value("a") b_val = self.cf_model.record(0).value("b") self.cf_eq = lambda x: a_val*np.exp(-b_val*x) def on_show_graph_check(self, state): self.show_graph = state == Qt.Checked def on_show_ssde_graph_check(self, state): self.show_ssde_graph = state == Qt.Checked def sliceopt_handle(self, value): self.d3_opts_cb.setCurrentIndex(value) self.on_mode_changed(value) def mode3d_changed_handle(self, value): rb = [r for r in self.d3opts_rbtns if r.text().lower()==value] rb[0].setChecked(True) def slice1_changed_handle(self, value): self.slice1_sb.setValue(value) def slice2_changed_handle(self, value): self.slice2_sb.setValue(value) def on_3d_opts_changed(self, sel): sel = self.sender() if sel.isChecked(): self.d3_method = sel.text().lower() if self.d3_method == 'regional': self.to_lbl.setHidden(False) self.slice2_sb.setHidden(False) self.slice1_sb.setMinimum(1) self.slice1_sb.setMaximum(self.ctx.total_img) else: self.to_lbl.setHidden(True) self.slice2_sb.setHidden(True) def on_mode_changed(self, idx): self.all_slices = idx==1 label_d = self.current_dlabel[:3]+'Avg. '+self.current_dlabel[3:] if self.all_slices else self.current_dlabel label_c = '<b>Avg. CTDI<sub>vol</sub> (mGy)</b>' if self.all_slices else '<b>CTDI<sub>vol</sub> (mGy)</b>' label_s = '<b>Avg. SSDE (mGy)</b>' if self.all_slices else '<b>SSDE (mGy)</b>' self.diameter_label.setText(label_d) self.ctdiv_label.setText(label_c) self.ssde_label.setText(label_s) self.d3opts_grpbox.setVisible(self.all_slices) self.ctx.app_data.s_mode = idx self.update_values() def get_idxs(self): if not self.ctx.isImage: self.idxs = [0] return dcms = np.array(self.ctx.dicoms) index = list(range(len(dcms))) idxs = index if self.all_slices: nslice = self.slice1_sb.value() if self.d3_method == 'slice step': idxs = index[::nslice] elif self.d3_method == 'slice number': tmps = np.array_split(np.arange(len(dcms)), nslice) idxs = [tmp[len(tmp)//2] for tmp in tmps] elif self.d3_method == 'regional': nslice2 = self.slice2_sb.value() first = nslice if nslice<=nslice2 else nslice2 last = nslice2 if nslice<=nslice2 else nslice idxs = index[first-1:last] self.idxs = [idx+1 for idx in idxs] def on_calculate(self): try: self.ctx.app_data.convf = self.cf_eq(self.ctx.app_data.diameter) except: QMessageBox.warning(None, "No Data", f"There are no data in {self.report_cb.currentText()} report for {self.ctx.phantom_name} phantom.") return if not self.all_slices: # if self.use_avg: # self.ctx.app_data.SSDE = self.ctx.app_data.convf * self.ctx.app_data.CTDIv # else: try: self.diameter = self.ctx.app_data.diameters[self.ctx.current_img] self.CTDIv = self.ctx.app_data.CTDIvs[self.ctx.current_img] except: QMessageBox.warning(None, "No Data", f"No CTDIv and diameter data for slice {self.ctx.current_img}.\nCalculate them first.") return self.convf = self.cf_eq(self.diameter) self.SSDE = self.convf * self.CTDIv self.DLPc = self.convf * self.ctx.app_data.DLP self.effdose = self.ctx.app_data.DLP * np.exp(self.alfa*self.diameter + self.beta) self.ctx.app_data.convfs[self.ctx.current_img] = self.convf self.ctx.app_data.SSDEs[self.ctx.current_img] = self.SSDE self.ctx.app_data.dlpcs[self.ctx.current_img] = self.DLPc self.ctx.app_data.effdoses[self.ctx.current_img] = self.effdose self.ctx.app_data.emit_s_changed() else: ctdivs = [] diameters = [] cond = self.d3_method==self.ctx.app_data.mode3d and self.slice1_sb.value()==self.ctx.app_data.slice1 if self.d3_method=='regional': cond = cond and self.slice2_sb.value()==self.ctx.app_data.slice2 self.get_idxs() idx_to_remove = [] for idx in self.idxs: if idx not in self.ctx.app_data.CTDIvs.keys() or idx not in self.ctx.app_data.diameters.keys(): idx_to_remove.append(idx) continue ctdivs.append(self.ctx.app_data.CTDIvs[idx]) diameters.append(self.ctx.app_data.diameters[idx]) if len(idx_to_remove)>0: [self.idxs.remove(idx) for idx in idx_to_remove if idx in self.idxs] self.ctdivs = np.array(ctdivs) self.diameters = np.array(diameters) convfs = self.cf_eq(self.diameters) self.ssdes = convfs * self.ctdivs dlpcs = convfs * self.ctx.app_data.DLP effdoses = self.ctx.app_data.DLP * np.exp(self.alfa*self.diameters + self.beta) self.diameter = self.diameters.mean() self.CTDIv = self.ctdivs.mean() self.SSDE = self.ssdes.mean() self.convf = self.cf_eq(self.diameter) self.DLPc = self.convf * self.ctx.app_data.DLP self.effdose = self.ctx.app_data.DLP * np.exp(self.alfa*self.diameter + self.beta) for idx, v in enumerate(self.idxs): self.ctx.app_data.convfs[v] = convfs[idx] self.ctx.app_data.SSDEs[v] = self.ssdes[idx] self.ctx.app_data.dlpcs[v] = dlpcs[idx] self.ctx.app_data.effdoses[v] = effdoses[idx] self.ctx.app_data.emit_s_changed() if self.show_ssde_graph: self.plot_ssde(self.idxs, self.ssdes) self.diameter_edit.setText(f'{self.diameter:#.2f}') self.ctdiv_edit.setText(f'{self.CTDIv:#.2f}') self.convf_edit.setText(f'{self.convf:#.2f}') self.ssde_edit.setText(f'{self.SSDE:#.2f}') self.dlpc_edit.setText(f'{self.DLPc:#.2f}') self.effdose_edit.setText(f'{self.effdose:#.2f}') self.ctx.app_data.effdose = self.effdose self.ctx.app_data.DLPc = self.DLPc self.set_data() if self.show_graph: minv = 6 if self.ctx.phantom == HEAD else 8 maxv = 55 if self.ctx.phantom == HEAD else 45 deff = np.arange(minv, maxv+1, 1) cf = self.cf_eq(deff) data = np.array([deff, cf]).T self.plot(data) self.switch_button_default(mode=1) def update_values(self, val=True): if not val: return try: self.CTDIv = self.ctx.app_data.CTDIvs[self.ctx.current_img] if not self.all_slices else self.ctdivs.mean() except: self.CTDIv = 0 try: self.diameter = self.ctx.app_data.diameters[self.ctx.current_img] if not self.all_slices else self.diameters.mean() except: self.diameter = 0 try: self.convf = self.ctx.app_data.convfs[self.ctx.current_img] if not self.all_slices else self.cf_eq(self.diameter) except: self.convf = 0 try: self.SSDE = self.ctx.app_data.SSDEs[self.ctx.current_img] if not self.all_slices else self.ssdes.mean() except: self.SSDE = 0 try: self.DLPc = self.ctx.app_data.dlpcs[self.ctx.current_img] if not self.all_slices else self.cf_eq(self.diameter) * self.ctx.app_data.DLP except: self.DLPc = 0 try: self.effdose = self.ctx.app_data.effdoses[self.ctx.current_img] if not self.all_slices else self.ctx.app_data.DLP * np.exp(self.alfa*self.diameter + self.beta) except: self.effdose = 0 self.convf_edit.setText(f'{self.convf:#.2f}') self.ctdiv_edit.setText(f'{self.CTDIv:#.2f}') self.diameter_edit.setText(f'{self.diameter:#.2f}') self.ssde_edit.setText(f'{self.SSDE:#.2f}') self.dlpc_edit.setText(f'{self.DLPc:#.2f}') self.effdose_edit.setText(f'{self.effdose:#.2f}') self.set_data() def img_changed_handle(self, value): if value: self.update_values() # self.reset_fields() def reset_fields(self): self.initVar() self.ctx.app_data.convf = 0 self.ctx.app_data.SSDE = 0 self.ctx.app_data.DLPc = 0 self.ctx.app_data.effdose = 0 self.show_graph = False # self.d3_opts_cb.setCurrentIndex(0) # self.on_mode_changed(0) self.plot_chk.setCheckState(Qt.Unchecked) self.switch_button_default() self.ctdiv_edit.setText(f'{0:#.2f}') self.diameter_edit.setText(f'{0:#.2f}') self.dlp_edit.setText(f'{0:#.2f}') self.convf_edit.setText(f'{0:#.2f}') self.ssde_edit.setText(f'{0:#.2f}') self.dlpc_edit.setText(f'{0:#.2f}') self.effdose_edit.setText(f'{0:#.2f}') def on_check(self): print('ctdi', self.ctx.app_data.CTDIvs) print('diameter', self.ctx.app_data.diameters) print('ssde', self.ctx.app_data.SSDEs) print('cf', self.ctx.app_data.convfs) print('dlpc', self.ctx.app_data.dlpcs) print('efd', self.ctx.app_data.effdoses)
class ApiDatabaseDialog(QDialog): def __init__(self): super().__init__() self.setMinimumSize(QSize(800, 400)) config_folder = os.path.join(os.path.expanduser("~"), '.config', 'CSV_Viewer') os.makedirs(config_folder, exist_ok=True) db_file = "apilinks.sqlite" db_path = os.path.join(config_folder, db_file) self.db = QSqlDatabase("QSQLITE") self.db.setDatabaseName(db_path) if self.db.open(): if os.path.getsize(db_path) == 0: query = QSqlQuery(db=self.db) query.exec_("CREATE TABLE links(address TEXT)") demo = "http://climatedataapi.worldbank.org/climateweb/rest/v1/country/cru/tas/year/POL.csv" query.exec_(f"INSERT INTO links VALUES ('{demo}')") self.db.commit() else: QMessageBox.warning(self, "Error", "API links database not open.") self.table = QTableView() self.model = QSqlTableModel(db=self.db) self.model.setTable('links') self.model.setEditStrategy(QSqlTableModel.OnFieldChange) self.model.select() # set headers column_titles = {"address": "API Address"} for n, t in column_titles.items(): idx = self.model.fieldIndex(n) self.model.setHeaderData(idx, Qt.Horizontal, t) self.table.setModel(self.model) self.table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) self.table.setSelectionMode(QtWidgets.QTableView.SingleSelection) self.table.setColumnWidth(0, 750) self.table.selectRow(0) self.table.setFocus() self.layout = QVBoxLayout() QBtn = QDialogButtonBox.Ok self.buttonBox = QDialogButtonBox(QBtn) style_add = self.buttonBox.style() icon = style_add.standardIcon(QStyle.SP_DialogYesButton) self.button_add = QPushButton(icon, "&Add") self.button_add.setStatusTip("Add new api link") self.button_add.clicked.connect(self.add_link) style_del = self.buttonBox.style() icon = style_del.standardIcon(QStyle.SP_DialogCloseButton) self.button_del = QPushButton(icon, "&Delete") self.button_del.setStatusTip("Delete api link") self.button_del.clicked.connect(self.del_link) self.buttonBox.accepted.connect(self.accept) self.layout.addWidget(self.table) layout_btn = QHBoxLayout() layout_btn.addWidget(self.button_add) layout_btn.addWidget(self.button_del) layout_btn.addSpacerItem(QSpacerItem(150, 10, QSizePolicy.Expanding)) layout_btn.addWidget(self.buttonBox) self.layout.addLayout(layout_btn) self.setLayout(self.layout) def closeEvent(self, event) -> None: """ Quit dialog """ self.db.close() def add_link(self): self.model.insertRows(self.model.rowCount(), 1) self.table.setFocus() self.table.selectRow(self.model.rowCount() - 1) index = self.table.currentIndex() self.table.edit(index) def del_link(self): if self.model.rowCount() > 0: index = self.table.currentIndex() self.model.removeRow(index.row()) self.model.submitAll() self.table.setRowHidden(index.row(), True) if index.row() == 0: current = 0 else: current = index.row() - 1 self.table.selectRow(current)
class PropertiesService(QObject): controller = None # type: PropertiesController question_type_model = None # type: QSqlTableModel question_type_filter_proxy_model = None # type: 'QuestionTypeFilterProxyModel' def __init__(self, parent=None): QObject.__init__(self, parent) self.connect_slots() def connect_slots(self): application.app.aboutToQuit.connect( self.on_about_to_quit ) def dispose(self): pass def show(self, tab_wanted: str = 'general'): from genial.services import document_service if document_service.database is not None: if self.question_type_model is None: self.question_type_model = QSqlTableModel( self, document_service.database ) self.question_type_model.setTable("question_type") self.question_type_model.setEditStrategy( QSqlTableModel.OnManualSubmit ) self.question_type_filter_proxy_model = QuestionTypeFilterProxyModel() self.question_type_filter_proxy_model.setSourceModel( self.question_type_model ) self.question_type_filter_proxy_model.sort( self.question_type_model.fieldIndex("position"), Qt.AscendingOrder ) self.question_type_filter_proxy_model.setDynamicSortFilter(True) if self.controller is None: self.controller = PropertiesController() self.controller.start() self.controller.show(tab_wanted) @pyqtSlot() def on_about_to_quit(self): self.dispose()