def get_table(self, list_name, table_name): """ Returns a QTableWidget showing an interaction parameter matrix for a given composition :param list_name: str, name of component list of the corresponding table :param table_name: str, name of table (could be K, alpha, A, B, C. epsilon, sigma, gamma dep on mixing rule) :return: QTableWidget, table containing the correct parameters """ composition = self.component_lists[list_name]["Names"] matrix_data = self.settings["Parameters"][list_name]["Coefficient matrices"][table_name] size = len(composition) table = QTableWidget(size, size) # Insert coefficients into table for row in range(size): table.setVerticalHeaderItem(row, QTableWidgetItem(composition[row])) for col in range(size): table.setHorizontalHeaderItem(col, QTableWidgetItem(composition[col])) for i in range(size): for j in range(size): if i == j: item = QTableWidgetItem("-") # Not editable item.setFlags(QtCore.Qt.NoItemFlags) else: item = QTableWidgetItem(str(matrix_data[i][j])) item.setTextAlignment(QtCore.Qt.AlignCenter) table.blockSignals(True) table.setItem(i, j, item) table.blockSignals(False) if table_name in ["VDW K", "CPA K", "CPA Epsilon", "SAFT-VR Mie Epsilon", "SAFT-VR Mie Sigma", "SAFT-VR Mie Gamma"]: # Matrix is symmetric, so these items can't be edited if i >= j: item.setFlags(QtCore.Qt.NoItemFlags) header = table.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeToContents) return table
class VideoTagEditor(QWidget): def __init__(self, db): super(VideoTagEditor, self).__init__() self.path = None self.db = db self.initUi() def initUi(self): self.setLayout(QVBoxLayout()) self.video = BasicVideoWidget() self.layout().addWidget(self.video, 1) self.row2 = QWidget() self.row2.setLayout(QHBoxLayout()) self.layout().addWidget(self.row2) self.playPauseButton = QPushButton('▶') self.playPauseButton.clicked.connect(self.playPause) self.row2.layout().addWidget(self.playPauseButton) self.seeker = SeekSlider(self.video) self.seeker.setSingleStep(1000) #~ self.seeker.valueChanged.connect(self.video.seek) self.row2.layout().addWidget(self.seeker) self.posLabel = PositionLabel(self.video) self.row2.layout().addWidget(self.posLabel) startSet = QPushButton('<') startSet.clicked.connect(self.rowSetStart) self.layout().addWidget(startSet) endSet = QPushButton('>') endSet.clicked.connect(self.rowSetEnd) self.layout().addWidget(endSet) self.endRow = QWidget() self.endRow.setLayout(QHBoxLayout()) self.layout().addWidget(self.endRow) self.table = QTableWidget() self.table.setColumnCount(3) self.table.setHorizontalHeaderLabels( ['Tags', 'Start time', 'End time']) self.table.itemChanged.connect(self._itemChanged) self.table.itemDoubleClicked.connect(self._itemDClicked) self.layout().addWidget(self.table) self.addRowButton = QPushButton('Add') self.addRowButton.clicked.connect(self.addRow) self.endRow.layout().addWidget(self.addRowButton) self.delRowButton = QPushButton('Remove') self.delRowButton.clicked.connect(self.delRow) self.endRow.layout().addWidget(self.delRowButton) def setFile(self, path): self.path = path if self.video.state() == QMediaPlayer.PlayingState: self.video.pause() self.video.load(path) self.loadTags() @Slot() def playPause(self): if self.video.state() != QMediaPlayer.PlayingState: self.video.play() self.playPauseButton.setText('▋▋') else: self.video.pause() self.playPauseButton.setText('▶') def chooseTags(self, row): row = self.table.currentRow() taglist = self.table.item(row, 0).text().split(' ') dialog = TagChooserDialog(self.db) dialog.setTags(list(taglist)) dialog.exec_() taglist = dialog.selectedTags() self.table.item(row, 0).setText(' '.join(taglist)) #~ self.saveTags() @Slot() def rowSetStart(self): ms = self.video.position() n = self.table.currentRow() self.table.item(n, 1).setText('%d' % (ms / 1000)) #~ self.saveTags() @Slot() def rowSetEnd(self): ms = self.video.position() n = self.table.currentRow() self.table.item(n, 2).setText('%d' % (ms / 1000)) #~ self.saveTags() @Slot() def delRow(self): self.table.removeRow(self.table.currentRow()) self.saveTags() @Slot() def addRow(self): n = self.table.rowCount() self.table.insertRow(n) for col in range(self.table.columnCount()): self.table.setItem(n, col, QTableWidgetItem()) def loadTags(self): blocking = self.table.signalsBlocked() try: self.table.blockSignals(True) tags = self.db.find_tags_by_file(self.path) for t in tags: for start, end in self.db.get_extras_for_file(self.path, t): n = self.table.rowCount() self.table.insertRow(n) self.table.setItem(n, 0, QTableWidgetItem(t)) self.table.setItem(n, 1, QTableWidgetItem('%d' % (start / 1000))) self.table.setItem(n, 2, QTableWidgetItem('%d' % (end / 1000))) finally: self.table.blockSignals(blocking) def saveTags(self): duration = self.video.duration() / 1000 with self.db: self.db.remove_file(self.path) for i in range(self.table.rowCount()): tags = self.table.item(i, 0).text().split(' ') tags = [t for t in tags if t] if not tags: continue start = int(self.table.item(i, 1).text() or 0) * 1000 end = int(self.table.item(i, 2).text() or duration) * 1000 for tag in tags: self.db.tag_file(self.path, tag, start, end) def _itemChanged(self, qitem): self.saveTags() pass def _itemDClicked(self, item): if item.column() != 0: pass self.chooseTags(item.row())
class Labels(QWidget): """ Attributes ---------- chan_name : list of str list of all the labels (with the user-defined changes) """ def __init__(self, parent): super().__init__() self.parent = parent self.filename = None self.chan_name = None # None when dataset is not loaded self.create() def create(self): self.idx_load = QPushButton('Load') self.idx_load.clicked.connect(self.load_labels) self.idx_load.setToolTip('Load file with a list of channels (separated by , or ; or tabs or spaces).') self.idx_save = QPushButton('Save') self.idx_save.clicked.connect(self.save_labels) self.idx_save.setEnabled(False) # cancel is equal to setting labels to what they were self.idx_cancel = QPushButton('Cancel') self.idx_cancel.clicked.connect(self.update) self.idx_apply = QPushButton('Apply') self.idx_apply.clicked.connect(self.apply) self.idx_apply.setToolTip('Changes will take effect. This will reset the channel groups and traces.') layout_0 = QHBoxLayout() layout_0.addWidget(self.idx_load) layout_0.addWidget(self.idx_save) layout_1 = QHBoxLayout() layout_1.addWidget(self.idx_cancel) layout_1.addWidget(self.idx_apply) self.table = QTableWidget() self.table.horizontalHeader().setStretchLastSection(True) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(['Current Labels', 'New Labels']) self.table.cellChanged.connect(self.check_labels) layout = QVBoxLayout() layout.addLayout(layout_0) layout.addWidget(self.table) layout.addLayout(layout_1) self.setLayout(layout) self.setEnabled(False) def update(self, checked=False, labels=None, custom_labels=None): """Use this function when we make changes to the list of labels or when we load a new dataset. Parameters ---------- checked : bool argument from clicked.connect labels : list of str list of labels in the dataset (default) custom_labels : list of str list of labels from a file """ if labels is not None: self.setEnabled(True) self.chan_name = labels self.table.blockSignals(True) self.table.clearContents() self.table.setRowCount(len(self.chan_name)) for i, label in enumerate(self.chan_name): old_label = QTableWidgetItem(label) old_label.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if custom_labels is not None and i < len(custom_labels) and custom_labels[i]: # it's not empty string or None label_txt = custom_labels[i] else: label_txt = label new_label = QTableWidgetItem(label_txt) self.table.setItem(i, 0, old_label) self.table.setItem(i, 1, new_label) self.table.blockSignals(False) def check_labels(self): # read new labels first labels = self._read_labels() # disable apply, if there are duplicates if len(labels) == len(set(labels)): self.idx_apply.setEnabled(True) else: self.idx_apply.setEnabled(False) # mark duplicates in red self.table.blockSignals(True) for i, label in enumerate(labels): if labels.count(label) > 1: self.table.item(i, 1).setBackground(QColor('red')) else: self.table.item(i, 1).setBackground(QColor('white')) self.table.blockSignals(False) def load_labels(self, checked=False, test_name=None): if self.filename is not None: filename = self.filename elif self.parent.info.filename is not None: filename = Path(self.parent.info.filename) else: filename = None if test_name: filename = test_name else: filename, _ = QFileDialog.getOpenFileName(self, 'Open Labels', str(filename.parent), 'Comma-separated values (*.csv);; Text file (*.txt);; All Files(*.*)') if filename == '': return self.filename = Path(filename) with self.filename.open() as f: text = f.read() labels = split(', |,|; |;|\t|\n| ', text) labels = [label.strip() for label in labels] self.update(custom_labels=labels) def save_labels(self): """Save labels modified by the user. TODO ---- Save labels modified by the user """ pass def apply(self): self.chan_name = self._read_labels() self.parent.info.dataset.header['chan_name'] = self.chan_name self.parent.channels.reset() self.parent.channels.update() self.parent.traces.reset() def reset(self): self.table.blockSignals(True) self.table.clearContents() self.table.blockSignals(False) self.setEnabled(False) def _read_labels(self): labels = [] for i in range(self.table.rowCount()): labels.append(self.table.item(i, 1).text()) return labels
class FS3MainWindow(QMainWindow, FORM_CLASS): """ FS3MainWindow Handles the creation and display of the window. Makes use of the fs3.ui QT ui file. """ resized = pyqtSignal() def __init__(self, parent=None): super(FS3MainWindow, self).__init__(parent) self.setupUi(self) self.mainWindowSplitter.setStretchFactor(1, 10) self.setWindowTitle('FS3 -- FieldStats3') self.iconPath = ":/plugins/FS3/FS3Icon.png" self.setWindowIcon(QIcon(self.iconPath)) self.fieldGetterInst = LayerFieldGetter() self.grapher = Grapher(self.graphTypeBox) self.currentProject = QgsProject.instance() self.currentLayer = None self.allFields = None self.emptyCellDict = {} #{feature_id:cell} self.percentile25Update() self.currentDecimalPrecision = 0 self.error = QErrorMessage() ### Tabs self.tabFields.currentChanged.connect(self.graphTabLoaded) self.dataTableLayout = QVBoxLayout() self.tableWidget = QTableWidget() ### Data Table Widget Connection self.tableWidget.cellChanged.connect(self.attributeCellChanged) self.horizontalHeader = self.tableWidget.horizontalHeader() self.horizontalHeader.sectionClicked.connect(self.handleDataSortSignal) self.statisticLayout = QVBoxLayout() self.statisticTable = QTableWidget() self.uniqueLayout = QVBoxLayout() self.uniqueTable = QTableWidget() self.uniqueTable.verticalHeader().hide() self.graphLayout = QHBoxLayout() self.graphView = QWebView() self.uniqueHHeader = self.uniqueTable.horizontalHeader() self.uniqueHHeader.sectionClicked.connect(self.handleUniqueSortSignal) ### Window resized self.resizeTimer = QTimer() self.resizeTimer.setSingleShot(True) self.resizeTimer.timeout.connect(self.windowTimeout) self.resized.connect(self.windowResized) ###Background Color Brush self.backgroundBrush = QColor.fromRgb(230, 230, 250) self.defaultBrush = QColor.fromRgbF(0, 0, 0, 0) #Refresh for the connecters self.refresh() ###pyqtSlot connectors (PROJECT) self.currentProject.layersAdded.connect(self.refresh) self.currentProject.layersRemoved.connect(self.refresh) ### pyqtSlot connectors (BUTTONS) # Percentile self.percentile25.clicked.connect(self.percentile25Update) self.percentile10.clicked.connect(self.percentile10Update) self.percentile5.clicked.connect(self.percentile5Update) self.percentileHighEnd.clicked.connect(self.percentileHighEndUpdate) self.percentilesLineEdit.textChanged.connect( self.percentileTextChanged) # Limit to Selected self.limitToSelected.stateChanged.connect(self.handleLimitSelected) # Toggle Edit Mode self.editModeCheck.stateChanged.connect(self.handleEditModeChecked) # Decimal Selector self.numberOfDecimalsBox.valueChanged.connect( self.handleDecimalChanged) ### Layer Combo Box and Field List Widget self.selectLayerComboBox.currentIndexChanged \ .connect(self.refreshFields) self.selectFieldListWidget.itemSelectionChanged \ .connect(self.refreshAttributes) ### Handles graph stuffs self.graphTypeBox.currentIndexChanged.connect(self.refreshAttributes) self.openGraphSettings.clicked.connect(self.grapher.openGraphOptions) self.grapher.optionsWindow.applyButton.clicked.connect( self.refreshGraph) self.pngExportButton.clicked.connect(self.exportToPNG) self.htmlOpenButton.clicked.connect(self.openHTML) @pyqtSlot() def openGraphOptions(self): """ openGraphOptions Opens the Graph Options UI """ self.grapher.openGraphOptions() def refresh(self): """ refresh Reloads data """ self.refreshLayers() self.refreshFields() self.refreshAttributes() @pyqtSlot() def percentile25Update(self): """ percentile25Update Fills LineEdit with 25th percentile numbers """ self.percentilesLineEdit.setText("25, 50, 75, 100") @pyqtSlot() def percentile10Update(self): """ percentile10Update Fills LineEdit with 10th percentile numbers """ percentile10Str = ", ".join(str(x * 10) for x in range(1, 11)) self.percentilesLineEdit.setText(percentile10Str) @pyqtSlot() def percentile5Update(self): """ percentile5Update Fills LineEdit with 5th percentile numbers """ percentile5Str = ", ".join(str(x * 5) for x in range(1, 21)) self.percentilesLineEdit.setText(percentile5Str) @pyqtSlot() def percentileHighEndUpdate(self): """ percentileHighEndUpdate Fills LineEdit with High End (50, 80, 95) percentile numbers """ self.percentilesLineEdit.setText("50, 80, 95") @pyqtSlot() def percentileTextChanged(self): """ percentileTextChanged Tokenize percentile display """ try: percentileList = self.percentilesLineEdit.text().split(', ') for percentile in percentileList: # Ensure the user has entered a valid percentile if float(percentile) < 0 or float(percentile) > 100: return self.refreshAttributes() except ValueError: # The user is either still entering text # Or has entered an invalid input return @pyqtSlot() def handleLimitSelected(self): """ handleLimitSelected Limit to selected checkbox """ self.refreshAttributes() @pyqtSlot() def handleEditModeChecked(self): """ handleEditModeChecked Edit Mode trigger within plugin UI """ # Edit box was checked # Check state if self.editModeCheck.isChecked(): # Check if the layer is already in edit mode if self.currentLayer.isEditable(): # Our work here is already done return # Else set the layer to editable editMessage = QCoreApplication.translate( "FS3MainWindow", "FS3 Performance may be slow while in edit mode.") editMessage += QCoreApplication.translate( "FS3MainWindow", "\nPlease ensure it is disabled when not in use.") self.error.showMessage(editMessage) self.currentLayer.startEditing() else: # Else the checkbox is unchecked # Check state of the layer if not self.currentLayer.isEditable(): # Our work here is already done return # Else set the layer to noneditable self.currentLayer.commitChanges() @pyqtSlot() def editingStartedQGIS(self): """ editingStartedQGIS Handles events when in Edit Mode """ if self.editModeCheck.isChecked(): # The checkbox is already checked, return return self.editModeCheck.setChecked(True) @pyqtSlot() def editingStoppedQGIS(self): """ editingStoppedQGIS Handles events when out of Edit Mode """ if not self.editModeCheck.isChecked(): # The checkbox is already unchecked, return return self.editModeCheck.setChecked(False) @pyqtSlot('int', 'int') def attributeCellChanged(self, row, column): """ attributeCellChanged Handles updates case any change were made in Edit Mode @param row Row of data table cell that was changed @param column Column of data table cell that was changed """ newValue = self.tableWidget.item(row, column) if self.currentLayer.isEditable(): # Make the change then commit the change fieldname = self.tableWidget.horizontalHeaderItem(column).text() for fid, cell in self.emptyCellDict.items(): feature = self.currentLayer.getFeature(fid) fieldIndex = feature.fieldNameIndex(fieldname) if cell == newValue: success = self.currentLayer.changeAttributeValue( fid, fieldIndex, newValue.text()) if success: # Update was successful, commit changes successCommit = self.currentLayer.commitChanges() if not successCommit: commitError = str( (len(self.currentLayer.commitErrors()))) commitError += '\n' + str( (self.currentLayer.commitErrors()[0])) self.error.showMessage(commitError) else: #Else the operation was a success self.currentLayer.startEditing() else: # Update failed, report error updateError = QCoreApplication.translate( "FS3MainWindow", "Attribute update failed") self.error.showMessage(updateError) @pyqtSlot() def handleDataSortSignal(self): """ handleDataSortSignal Handles propper sorting of statistic data """ #Recolor the table for i in range(0, self.tableWidget.columnCount()): for j in range(0, self.tableWidget.rowCount()): cell = self.tableWidget.item(j, i) if (j % 2) == 0: #This is an even row, color it cell.setBackground(self.backgroundBrush) else: #This is an odd row, default its color cell.setBackground(self.defaultBrush) @pyqtSlot() def handleUniqueSortSignal(self): """ handleUniqueSortSignal Handles propper sorting of unique data """ #Recolor the table for i in range(0, self.uniqueTable.columnCount()): for j in range(0, self.uniqueTable.rowCount()): cell = self.uniqueTable.item(j, i) if (j % 2) == 0: #This is an even row, color it cell.setBackground(self.backgroundBrush) else: #This is an odd row, default its color cell.setBackground(self.defaultBrush) @pyqtSlot() def handleSelectionChanged(self): """ handleSelectionChanged Update on a new selection """ #If there are selected layers in QGIS self.refreshAttributes() @pyqtSlot() def handleDecimalChanged(self): """ handleDecimalChanged Handles Decimal selection box """ self.currentDecimalPrecision = self.numberOfDecimalsBox.value() self.refreshAttributes() def refreshLayers(self): """ refreshLayers Reload the layers coboBox with the current content of the layers list """ self.selectLayerComboBox.clear() layers = self.fieldGetterInst.getVectorLayers() self.selectLayerComboBox.insertItems(0, layers) @pyqtSlot() def refreshFields(self): """ refreshFields Reload the fields coboBox with the current content of the field lists """ self.selectFieldListWidget.clear() layer = self.fieldGetterInst.getSingleLayer \ (self.selectLayerComboBox.currentText()) if layer != None: self.currentLayer = layer self.allFields = self.currentLayer.fields() self.selectFieldListWidget.insertItem( 0, QCoreApplication.translate("FS3MainWindow", "All")) self.selectFieldListWidget.insertItems \ (1, self.fieldGetterInst.getAllFields(layer)) # 3 is Extended, 2 is Multi, check the documentation self.selectFieldListWidget.setSelectionMode(3) # Connect the current layer to a pyqtSlot self.currentLayer.selectionChanged.connect( self.handleSelectionChanged) #Listen for editing mode enabled and disabled self.currentLayer.editingStarted.connect(self.editingStartedQGIS) self.currentLayer.editingStopped.connect(self.editingStoppedQGIS) # Update grapher layer self.grapher.setData(self.currentLayer) @pyqtSlot() def refreshAttributes(self): """ refreshAttributes Refresh the table content with coresponding Layer & field selection """ self.tableWidget.setSortingEnabled(False) self.emptyCellDict.clear() self.tableWidget.blockSignals(True) self.tableWidget.clear() # Get selected fields form list widget selectedFields = self.selectFieldListWidget.selectedItems() fields = [field.text() for field in selectedFields] # If the field is not set yet (Layer was swapped) # Return until the refresh is ready if len(fields) is 0: return if self.limitToSelected.isChecked(): if not self.currentLayer.getSelectedFeatures().isClosed(): # If there are features selected, get them features = self.currentLayer.getSelectedFeatures() else: # Else get all features features = self.currentLayer.getFeatures() else: features = self.currentLayer.getFeatures() # Determine length total = 0 for feature in features: total += 1 self.tableWidget.setRowCount(total) if self.limitToSelected.isChecked(): if not self.currentLayer.getSelectedFeatures().isClosed(): # If there are features selected, get them features = self.currentLayer.getSelectedFeatures() else: # Else get all features features = self.currentLayer.getFeatures() else: features = self.currentLayer.getFeatures() # Data to pass to statistical calculations statValues = [] for i in range(len(fields)): statValues.append([]) # for each row row = 0 for feature in features: if QCoreApplication.translate("FS3MainWindow", "All") in fields: attributes = feature.attributes() self.tableWidget.setColumnCount(len(attributes)) col = 0 # for each column value for attribute in attributes: #Set the precision of numeric fields if isinstance(attribute, float): attribute = decimalRound(attribute, self.currentDecimalPrecision) if attribute == NULL: cell = MyTableWidgetItem("") self.tableWidget.setItem(row, col, cell) self.emptyCellDict[feature.id()] = cell else: cell = MyTableWidgetItem(str(attribute)) self.tableWidget.setItem(row, col, cell) self.emptyCellDict[feature.id()] = cell col += 1 else: self.tableWidget.setColumnCount(len(fields)) col = 0 for field in fields: fieldIndex = feature.fieldNameIndex(field) attribute = feature.attributes()[fieldIndex] if isinstance(attribute, float): attribute = decimalRound(attribute, self.currentDecimalPrecision) statValues[col].append(attribute) if attribute == NULL: cell = MyTableWidgetItem("") self.tableWidget.setItem(row, col, cell) self.emptyCellDict[feature.id()] = cell else: cell = MyTableWidgetItem(str(attribute)) self.tableWidget.setItem(row, col, cell) self.emptyCellDict[feature.id()] = cell col += 1 row += 1 featureInst = feature if QCoreApplication.translate("FS3MainWindow", "All") in fields: fields = self.fieldGetterInst.getAllFields(self.currentLayer) self.tableWidget.setHorizontalHeaderLabels(fields) else: self.tableWidget.setHorizontalHeaderLabels(fields) statistics = [] data = [] uniquenesses = [] for i in range(len(fields)): fieldIndex = featureInst.fieldNameIndex(fields[i]) uniquenesses.append(statValues[i]) if self.allFields.at(fieldIndex).isNumeric(): #Generate numeric statistics statistics.append( self.createNumericalStatistics(statValues[i])) data.append(statValues[i]) else: #Generate character statistics statistics.append( self.createCharacterStatistics(statValues[i])) data.append(statValues[i]) uniqueCalculation = self.createUniqueness(uniquenesses) self.grapher.setData(self.currentLayer, data, uniqueCalculation, self.limitToSelected.isChecked(), fields) self.refreshUnique(fields, uniqueCalculation) self.refreshStatistics(fields, statistics) self.refreshGraph() self.dataTableLayout.addWidget(self.tableWidget) self.dataTab.setLayout(self.dataTableLayout) self.tableWidget.blockSignals(False) self.tableWidget.setSortingEnabled(True) self.handleDataSortSignal() def createNumericalStatistics(self, inputArray): """ createNumericalStatistics Methods that instantiates Numerical Statistics and initializes them @param inputArray Data from selected field(s) that statistics should be run on @return numericalStatistics Finished Statistics Object """ percentileArray = [] try: percentileArray = self.percentilesLineEdit.text().split(', ') for percentile in percentileArray: if float(percentile) < 0 or float(percentile) > 100: raise ValueError except ValueError: self.error.showMessage( QCoreApplication.translate( "FS3MainWindow", "Invalid Value for Percentile Detected!")) percentileArray = [] originalSize = len(inputArray) emptyCellsRemoved = removeEmptyCells(inputArray) numericalStatistics = FS3NumericalStatistics() numericalStatistics.initialize(emptyCellsRemoved, percentileArray, originalSize) numericalStatistics.roundNumericStatistics( self.currentDecimalPrecision) return numericalStatistics def createCharacterStatistics(self, inputArray): """ createNumericalStatistics Methods that instantiates Character Statistics and initializes them @param inputArray Data from selected field(s) that statistics should be run on @return characterStatistics Finished Statistics Object """ percentileArray = [] try: percentileArray = self.percentilesLineEdit.text().split(', ') for percentile in percentileArray: if float(percentile) < 0 or float(percentile) > 100: raise ValueError except ValueError: self.error.showMessage( QCoreApplication.translate( "FS3MainWindow", "Invalid Value for Percentile Detected!")) percentileArray = [] originalSize = len(inputArray) emptyCellsRemoved = removeEmptyCells(inputArray) characterStatistics = FS3CharacterStatistics() characterStatistics.initialize(emptyCellsRemoved, percentileArray, originalSize) characterStatistics.roundCharacterStatistics( self.currentDecimalPrecision) return characterStatistics def createUniqueness(self, inputArray): """ createUniqueness Method that instantiates Uniqueness class and initializes it @param inputArray Data from selected field(s) that uniqueness should be run on @return uniqueness FS3Uniqueness object with calculated uniqueness """ uniqueness = FS3Uniqueness() uniqueness.initialize(inputArray) uniqueness.roundUniqueness(self.currentDecimalPrecision) return uniqueness def refreshStatistics(self, fields, stats): """ refreshStatistics Method that updates the statistic table @param fields Fields that are selected @param stats The current stats that are displayed """ numAndCharStats = False verticalHeaders = stats[0].statName for i in range(len(stats)): for j in range(len(stats)): if isinstance(stats[i], FS3NumericalStatistics): if isinstance(stats[j], FS3CharacterStatistics): numAndCharStats = True verticalHeaders = stats[i].statName + stats[j].statName break self.statisticTable.clear() self.statisticTable.setRowCount(stats[0].statCount + stats[0].statCount * numAndCharStats) self.statisticTable.setColumnCount(len(fields)) self.statisticTable.setVerticalHeaderLabels(verticalHeaders) self.statisticTable.setHorizontalHeaderLabels(fields) col = 0 for stat in stats: #See if the field is numeric if isinstance(stat, FS3NumericalStatistics): emptyData = stat.totalItemCount - stat.itemCount row = 0 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.totalItemCount))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.itemCount))) row += 1 self.statisticTable.setItem(row, col, QTableWidgetItem(str(emptyData))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.maxValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.minValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.meanValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.medianValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.modeValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.sumValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.stdDevValue))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.coeffVarValue))) row += 1 for percentile in stat.percentiles: self.statisticTable.setItem( row, col, QTableWidgetItem(str(percentile))) row += 1 else: # is character statistics row = 0 if numAndCharStats: row = stat.statCount emptyData = stat.totalItemCount - stat.itemCount self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.totalItemCount))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.itemCount))) row += 1 self.statisticTable.setItem(row, col, QTableWidgetItem(str(emptyData))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.maxLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.minLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.meanLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.medianLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.modeLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.sumLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.stdDevLength))) row += 1 self.statisticTable.setItem( row, col, QTableWidgetItem(str(stat.coeffVarLength))) row += 1 for percentile in stat.percentiles: self.statisticTable.setItem( row, col, QTableWidgetItem(str(percentile))) row += 1 col += 1 self.statisticLayout.addWidget(self.statisticTable) self.statisticsTab.setLayout(self.statisticLayout) self.handleStatisticsColor() def handleStatisticsColor(self): """ handleStatisticsColor Method implements zebra colors with table rows """ for i in range(0, self.statisticTable.columnCount()): for j in range(0, self.statisticTable.rowCount()): cell = self.statisticTable.item(j, i) if cell is None: cell = MyTableWidgetItem("") self.statisticTable.setItem(j, i, cell) if (j % 2) == 0: #This is an even row, color it cell.setBackground(self.backgroundBrush) else: #This is an odd row, default its color cell.setBackground(self.defaultBrush) def refreshUnique(self, fields, unique): """ refreshUnique Method that updates the unique table @param fields Currently selected fields @param unique Currently displayed unique data """ #Start by clearing the layout self.uniqueTable.setSortingEnabled(False) row = 0 col = 0 self.uniqueTable.clear() self.uniqueTable.setRowCount(unique.totalValues) self.uniqueTable.setColumnCount(unique.statCount) horizontalHeaders = unique.statName #Append the names of the fields to the value field horizontalHeaders[0] += ' ([' for field in fields: horizontalHeaders[0] += field + '] , [' horizontalHeaders[0] = horizontalHeaders[0][:-5] horizontalHeaders[0] += '])' self.uniqueTable.setHorizontalHeaderLabels(horizontalHeaders) for value in unique.uniqueValues: cell = MyTableWidgetItem(str(value)) self.uniqueTable.setItem(row, col, cell) row += 1 row = 0 col += 1 for occurance in unique.uniqueNumOccur: cell = MyTableWidgetItem(str(occurance)) self.uniqueTable.setItem(row, col, cell) row += 1 row = 0 col += 1 for percent in unique.uniquePercent: cell = MyTableWidgetItem(str(percent)) self.uniqueTable.setItem(row, col, cell) row += 1 self.uniqueTable.setSortingEnabled(True) self.handleUniqueSortSignal() self.uniqueLayout.addWidget(self.uniqueTable) self.uniqueTab.setLayout(self.uniqueLayout) @pyqtSlot() def refreshGraph(self): """ refreshGraph Refreshes the graph on data change """ plot_path = self.grapher.makeGraph() self.graphView.load(QUrl.fromLocalFile(plot_path)) self.graphLayout.addWidget(self.graphView) self.graphFrame.setLayout(self.graphLayout) self.graphView.show() def resizeEvent(self, event): self.resized.emit() return super(FS3MainWindow, self).resizeEvent(event) @pyqtSlot() def windowResized(self): self.resizeTimer.start(250) def windowTimeout(self): currentTab = self.tabFields.currentWidget() if currentTab == self.graphTab: #Refresh the attributes to create a new graph self.refreshAttributes() @pyqtSlot() def graphTabLoaded(self): currentTab = self.tabFields.currentWidget() if currentTab == self.graphTab: #Refresh the attributes to create a new graph self.refreshAttributes() @pyqtSlot() def exportToPNG(self): # Attempt to pull a file location from the grapher options path = self.grapher.optionsWindow.pngExportEdit.text() if (path is None) or (path == ''): # The user has not entered a path yet pngError = QCoreApplication.translate( "FS3MainWindow", "Error: No export path detected!\n") pngError += QCoreApplication.translate( "FS3MainWindow", "Please open the graph settings window ") pngError += QCoreApplication.translate( "FS3MainWindow", "and set an export path for the image.") self.error.showMessage(pngError) return #If a filepath can be found, attempt to save the image to it path += '/FS3Graph.png' success = self.graphFrame.grab().save(path, format='PNG', quality=100) if not success: #Cannot write file to that filepath pngError = 'Error: Cannot write file to given filepath!\n' pngError += 'Please ensure the filepath was entered correctly ' pngError += 'and it is a writable directory' self.error.showMessage(pngError) return @pyqtSlot() def openHTML(self): # Pull the path of the temp directory directory = tempfile.gettempdir() path = directory + '/temp_plot_name.html' webbrowser.open('file://' + os.path.realpath(path))
class App(QWidget): def __init__(self): super().__init__() self.title = 'CSA Program Chapter Tracker' self.left = 50 self.top = 75 self.width = 1350 self.height = 700 self.init_UI() def init_UI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.create_student_table() self.create_record_table() self.recordTableWidget.setEditTriggers( QAbstractItemView.NoEditTriggers) self.create_calendar() # l is label self.lname = QLabel("Name", self) self.lnumber = QLabel("Student ID", self) self.lgrade = QLabel("Grade", self) self.lhours = QLabel("Hours", self) self.lcsa = QLabel("CSA", self) self.lstart = QLabel(self.my_calendar.selectedDate().toString(), self) self.lend = QLabel(self.my_calendar.selectedDate().toString(), self) self.lname.move(102.5, 540) self.lnumber.move(252.5, 540) self.lgrade.move(425, 540) self.lhours.move(562.5, 540) self.lcsa.move(710, 540) self.lstart.move(1025, 350) self.lend.move(1180, 350) self.lstart.setFrameStyle(QFrame.Panel | QFrame.Raised) self.lend.setFrameStyle(QFrame.Panel | QFrame.Raised) self.lstart.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.lstart.setAlignment(Qt.AlignCenter) self.lend.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.lend.setAlignment(Qt.AlignCenter) self.lstart.resize(self.lstart.sizeHint().width() + 6, self.lstart.sizeHint().height() + 3) self.lend.resize(self.lend.sizeHint().width() + 6, self.lend.sizeHint().height() + 3) # tb is textbox self.tbname = QLineEdit(self) self.tbnumber = QLineEdit(self) self.tbhours = QLineEdit(self) # b is combobox self.bgrade = QComboBox(self) self.bcsa = QComboBox(self) self.btimespan = QComboBox(self) self.bgrade.addItem("9th") self.bgrade.addItem("10th") self.bgrade.addItem("11th") self.bgrade.addItem("12th") self.bcsa.addItem("N/A") self.bcsa.addItem("CSA Community") self.bcsa.addItem("CSA Service") self.bcsa.addItem("CSA Achievement") self.btimespan.addItem("Weeks") self.btimespan.addItem("Months") self.btimespan.addItem("Custom") self.bgrade.move(368, 570) self.bcsa.move(652.5, 570) self.btimespan.move(815, 105) self.tbname.move(50, 570) self.tbnumber.move(200, 570) self.tbhours.move(510, 570) self.tbname.resize(140, 25) self.tbnumber.resize(165, 25) self.tbhours.resize(140, 25) self.bgrade.resize(140, 25) self.bcsa.resize(155, 25) self.bgrade.currentText() self.add_button = QPushButton('Add', self) self.add_button.setToolTip('Add student to database with given data') self.add_button.move(810, 567.5) self.add_button.clicked.connect(self.add_student) self.delete_button = QPushButton('Delete', self) self.delete_button.setToolTip('Delete selected rows from database') self.delete_button.move(810, 245) self.delete_button.clicked.connect(self.remove_students) self.print_button = QPushButton('Compile Data', self) self.print_button.setToolTip( 'Compile the current student data as a pdf') self.print_button.move(810, 65) self.print_button.resize(self.print_button.sizeHint()) self.print_button.clicked.connect(self.compile_pdf) self.generate_button = QPushButton('Generate Student', self) self.generate_button.setToolTip('Generate a random student') self.generate_button.move(810, 605) self.generate_button.resize(self.generate_button.sizeHint()) self.generate_button.clicked.connect(self.generate_students) self.start_date_button = QPushButton('Set Start', self) self.start_date_button.setToolTip( 'Set the start date for the data to compile at') self.start_date_button.move(1030, 315) self.start_date_button.resize(self.start_date_button.sizeHint()) self.start_date_button.clicked.connect(self.set_start_date) self.end_date_button = QPushButton('Set End', self) self.end_date_button.setToolTip( 'Set the end date for the data to compile to') self.end_date_button.move(1185, 315) self.end_date_button.resize(self.end_date_button.sizeHint()) self.end_date_button.clicked.connect(self.set_end_date) self.btimespan.resize(self.print_button.sizeHint()) self.layout = QVBoxLayout() self.layout.setContentsMargins(50, 70, 550, 200) self.layout.addWidget(self.recordTableWidget) self.layout.addWidget(self.studentTableWidget) self.layout.setSpacing(29) self.layout.setStretch(0, 3) self.layout.setStretch(1, 5) self.setLayout(self.layout) self.edit = True self.show() def create_student_table(self): self.studentTableWidget = QTableWidget() row_count = c.execute("SELECT COUNT(*) FROM studentdata").fetchone()[0] self.studentTableWidget.setRowCount(row_count) self.studentTableWidget.setColumnCount(5) self.studentTableWidget.setShowGrid(True) self.studentTableWidget.move(0, 0) self.studentTableWidget.setHorizontalHeaderLabels( ['Name', 'Student ID', 'Grade Level', 'Hours', 'CSA']) self.header = self.studentTableWidget.horizontalHeader() self.header.setSectionResizeMode(0, QHeaderView.Stretch) self.header.setSectionResizeMode(1, QHeaderView.ResizeToContents) self.header.setSectionResizeMode(2, QHeaderView.ResizeToContents) self.header.setSectionResizeMode(3, QHeaderView.ResizeToContents) self.header.setSectionResizeMode(4, QHeaderView.ResizeToContents) self.populate_student_data() self.studentTableWidget.itemChanged.connect(self.log_change) def create_record_table(self): self.recordTableWidget = QTableWidget() row_count = c.execute("SELECT COUNT(*) FROM recorddata").fetchone()[0] self.recordTableWidget.setRowCount(row_count) self.recordTableWidget.setColumnCount(4) self.recordTableWidget.setShowGrid(True) self.recordTableWidget.setHorizontalHeaderLabels( ['Date', 'Name', 'Student ID', 'Hours Added']) self.header = self.recordTableWidget.horizontalHeader() self.header.setSectionResizeMode(0, QHeaderView.Stretch) self.header.setSectionResizeMode(1, QHeaderView.ResizeToContents) self.header.setSectionResizeMode(3, QHeaderView.ResizeToContents) self.header.setSectionResizeMode(2, QHeaderView.ResizeToContents) self.populate_record_data() def create_calendar(self): self.my_calendar = QCalendarWidget(self) self.my_calendar.move(1000, 70) self.my_calendar.resize(300, 200) self.ldate = QLabel(self) self.ldate.move(1100, 280) self.ldate.setFrameStyle(QFrame.Panel | QFrame.Sunken) self.ldate.setLineWidth(2) self.my_calendar.clicked[QDate].connect(self.on_date_selection) self.ldate.setText(self.my_calendar.selectedDate().toString()) self.ldate.resize(self.ldate.sizeHint()) def on_date_selection(self, date): self.ldate.setText(date.toString()) self.ldate.resize(self.ldate.sizeHint()) def populate_student_data(self): c.execute("SELECT * FROM studentdata") student_data = c.fetchall() self.edit = False self.studentTableWidget.setRowCount(0) self.studentTableWidget.setRowCount(len(student_data)) for i in range(len(student_data)): self.studentTableWidget.setItem( i, 0, QTableWidgetItem(str(student_data[i][0]))) self.studentTableWidget.setItem( i, 1, QTableWidgetItem(str(student_data[i][1]))) self.studentTableWidget.setItem( i, 2, QTableWidgetItem(str(student_data[i][2]))) self.studentTableWidget.setItem( i, 3, QTableWidgetItem(str(student_data[i][3]))) self.studentTableWidget.setItem( i, 4, QTableWidgetItem(str(student_data[i][4]))) self.edit = True def populate_record_data(self): c.execute("SELECT * FROM recorddata") record_data = c.fetchall()[::-1] self.recordTableWidget.setRowCount(0) self.recordTableWidget.setRowCount(len(record_data)) for i in range(len(record_data)): self.recordTableWidget.setItem( i, 0, QTableWidgetItem(str(record_data[i][0]))) self.recordTableWidget.setItem( i, 1, QTableWidgetItem(str(record_data[i][1]))) self.recordTableWidget.setItem( i, 2, QTableWidgetItem(str(record_data[i][2]))) self.recordTableWidget.setItem( i, 3, QTableWidgetItem(str(record_data[i][3]))) def log_change(self, item): if self.edit: cols = {0: "name", 1: "number", 2: "grade", 3: "hours", 4: "csa"} c.execute("SELECT * FROM studentdata") student_data = c.fetchall() student_number = student_data[item.row()][1] student_name = student_data[item.row()][0] old_hours = student_data[item.row()][3] if item.column() == 3: hours = item.text() if hours.isdigit() == False: try: hours = eval(hours) c.execute( "UPDATE studentdata SET hours = ? WHERE number = ?", ( hours, student_number, )) self.update_csa(float(hours), student_number, item) c.execute("INSERT INTO recorddata VALUES (?, ?, ?, ?)", (datetime.now().strftime("%B %d, %Y %I:%M %p"), \ student_name, student_number, str('%.3f' % (float(hours)-old_hours)).rstrip('0').rstrip('.'),)) self.populate_record_data() except Exception as e: value_error = QMessageBox() value_error.setIcon(QMessageBox.Critical) value_error.setInformativeText( 'Please enter a valid expression or number') value_error.setWindowTitle("Error: Value") value_error.exec_() else: c.execute( "UPDATE studentdata SET hours = ? WHERE number = ?", ( hours, student_number, )) self.update_csa(float(hours), student_number, item) c.execute("INSERT INTO recorddata VALUES (?, ?, ?, ?)", (datetime.now().strftime("%B %d, %Y %I:%M %p"), \ student_name, student_number, str('%.3f' % (float(hours)-old_hours)).rstrip('0').rstrip('.'),)) self.populate_record_data() else: try: c.execute( "UPDATE studentdata SET {} = ? WHERE number = ?". format(cols[item.column()]), ( item.text(), student_number, )) if item.column() == 0 or item.column() == 1: c.execute( "UPDATE recorddata SET {} = ? WHERE number = ?". format(cols[item.column()]), ( item.text(), student_number, )) self.populate_record_data() except Exception as e: insert_error = QMessageBox() insert_error.setIcon(QMessageBox.Critical) insert_error.setInformativeText( 'Please check your Student ID is unique') insert_error.setWindowTitle("Error: Values") insert_error.exec_() replace_list = [ '/' * (i + 1) for i in range(len(student_data)) ] replace_existing = [ i[1] for i in student_data if i[1] in replace_list ] replace_new = [ i for i in replace_list if i not in replace_existing ] replace_new.sort(key=len) c.execute( "UPDATE studentdata SET number = ? WHERE number = ?", ( replace_new[0], student_number, )) c.execute( "UPDATE recorddata SET number = ? WHERE number = ?", ( replace_new[0], student_number, )) self.studentTableWidget.setItem( item.row(), item.column(), QTableWidgetItem(replace_new[0])) conn.commit() def update_csa(self, hours, student_number, item): csa_switcher = { 50: "CSA Community", 200: "CSA Service", 500: "CSA Achievement" } adjusted_hours = 500 if hours >= 500 else 200 if hours >= 200 else 50 if hours >= 50 else 0 csa_award = csa_switcher.get(adjusted_hours, "N/A") c.execute("UPDATE studentdata SET csa = ? WHERE number = ?", (csa_award, student_number)) conn.commit() try: self.studentTableWidget.blockSignals(True) self.studentTableWidget.setItem(item.row(), 4, QTableWidgetItem(str(csa_award))) self.studentTableWidget.setItem( item.row(), 3, QTableWidgetItem(str('%.3f' % hours).rstrip('0').rstrip('.'))) self.studentTableWidget.blockSignals(False) except Exception as e: print(e) c.execute("SELECT * FROM studentdata") @pyqtSlot() def add_student(self): # i is input iname = self.tbname.text() inumber = self.tbnumber.text() igrade = str(self.bgrade.currentText()) ihours = self.tbhours.text() icsa = str(self.bcsa.currentText()) try: for i in range(5): if [iname, inumber, igrade, ihours, icsa][i] == '': raise Exception int(ihours) c.execute('''INSERT INTO studentdata VALUES (?, ?, ?, ?, ?)''', ( iname, inumber, igrade, ihours, icsa, )) c.execute('''INSERT INTO recorddata VALUES (?, ?, ?, ?)''', ( datetime.now().strftime("%B %d, %Y %I:%M %p"), iname, inumber, ihours, )) conn.commit() self.populate_student_data() self.populate_record_data() except Exception as e: insert_error = QMessageBox() insert_error.setIcon(QMessageBox.Critical) insert_error.setInformativeText( 'Please check your Student ID is unique, all inputs are filled out, and hours is a number' ) insert_error.setWindowTitle("Error: Values") insert_error.exec_() @pyqtSlot() def remove_students(self): selected = self.studentTableWidget.selectedItems() selected_dict = {} for item in selected: if item.row() in selected_dict: selected_dict[item.row()] += 1 else: selected_dict[item.row()] = 1 c.execute('''SELECT * FROM studentdata''') student_data = c.fetchall() numbers = () for key in reversed(list(selected_dict.keys())): if selected_dict[key] == 5: self.studentTableWidget.removeRow(key) numbers += (student_data[key][1], ) c.execute( "DELETE FROM studentdata WHERE number IN ({})".format(", ".join( "?" * len(numbers))), numbers) c.execute( "DELETE FROM recorddata WHERE number IN ({})".format(", ".join( "?" * len(numbers))), numbers) self.populate_record_data() conn.commit() @pyqtSlot() def compile_pdf(self): pdf = FPDF() pdf.add_page() pdf.set_font("courier", size=12) if self.btimespan.currentText() == "Weeks": start_range = (datetime.now() - timedelta(7)) end_range = datetime.now() txt = "Last Week Report" elif self.btimespan.currentText() == "Months": start_range = (datetime.now() - timedelta(30)) end_range = datetime.now() txt = "Last Week Report" else: start_range = datetime.strptime(self.lstart.text(), "%a %b %d %Y") end_range = datetime.strptime(self.lend.text(), "%a %b %d %Y") txt = "Custom Report: %s - %s" % (start_range.strftime( "%B %d, %Y"), end_range.strftime("%B %d, %Y")) pdf.cell(200, 10, txt=txt, ln=1, align="C") c.execute('''SELECT * FROM recorddata''') datafetch = c.fetchall() record_data = [record for record in datafetch if datetime.strptime(record[0], ("%B %d, %Y %I:%M %p")) >= start_range and\ datetime.strptime(record[0], ("%B %d, %Y %I:%M %p")) <= end_range][::-1] if record_data == []: insert_error = QMessageBox() insert_error.setIcon(QMessageBox.Critical) insert_error.setInformativeText('No data to Compile') insert_error.setWindowTitle("Error: Data") insert_error.exec_() else: field1_max = len( max([str(record[0]) for record in record_data], key=len)) field2_max = len( max([str(record[1]) for record in record_data], key=len)) field3_max = len( max([str(record[2]) for record in record_data], key=len)) field4_max = len( max([str(record[3]) for record in record_data], key=len)) field_maxes = [field1_max, field2_max, field3_max, field4_max] xpos = 200 ypos = 15 yline = 35 iconst = 2.725 sconst = 2.4 spacer = 0 if sum(field_maxes) > 75 else int( (75 - sum(field_maxes)) / 3) line_count = 0 page_lines = 17 if spacer == 0: pdf.set_font("courier", size=9) spacer = 0 if sum(field_maxes) > 100 else int( (100 - sum(field_maxes)) / 3) iconst *= 0.75 sconst *= 0.75 for record in record_data: txt = '' for i in range(len(list(record))): txt += str( record[i]) + ' ' * int(field_maxes[i] - len(str(record[i])) + spacer) pdf.cell(xpos, ypos, txt=txt, ln=1, align="L") pdf.line(0, yline, 400, yline) yline += 15 line_count += 1 newx = 0 if line_count % page_lines == 0: yline = 10 title_space = 25 if line_count == page_lines else 10 for i in field_maxes[0:3]: newx += (i) * iconst + (spacer) * sconst pdf.line(newx, title_space, newx, 20 + 15 * page_lines) elif line_count == len(record_data): title_space = 25 if line_count < page_lines else 10 footer_space = 20 if line_count < page_lines else 10 for i in field_maxes[0:3]: newx += (i) * iconst + (spacer) * sconst pdf.line(newx, title_space, newx, footer_space + 15 * (line_count % page_lines)) if len(record_data) > page_lines: pdf.line(0, yline, 400, yline) c.execute('''SELECT name, number FROM studentdata''') student_data = c.fetchall() for student in student_data: if student[1] in [record[2] for record in record_data]: total_user_hours = str( round( sum([ record[3] for record in record_data if record[2] == student[1] ]), 3)) txt = ' ' * int( sum(field_maxes) + spacer * 3 - len(total_user_hours) - len(student[0]) - 2) + student[0] + ': ' + total_user_hours pdf.cell(xpos, ypos, txt=txt, ln=1, align="L") total_hours = str( round(sum([record[3] for record in record_data]), 3)) txt = ' ' * int( sum(field_maxes) + spacer * 3 - len(total_hours) - 13) + "Total Hours: " + total_hours pdf.cell(xpos, ypos, txt=txt, ln=1, align="L") pdf.output("community_service_program.pdf") #generate random student with hours added on a random date @pyqtSlot() def generate_students(self): start_dt = date.today().replace(day=1, month=1).toordinal() end_dt = date.today().toordinal() random_day = date.fromordinal(random.randint( start_dt, end_dt)).strftime("%B %d, %Y %I:%M %p") random_name = fake.name() random_id = fake.ssn() random_grade = random.choice(['9th', '10th', '11th', '12th']) random_hours = random.randint(1, 500) csa_award = 'CSA Community' if random_hours >= 500 else ( 'CSA Service' if random_hours >= 200 else ('CSA Achievement' if random_hours >= 50 else 'N/A')) c.execute('''INSERT INTO studentdata VALUES (?, ?, ?, ?, ?)''', ( random_name, str(random_id), random_grade, random_hours, csa_award, )) c.execute('''INSERT INTO recorddata VALUES (?, ?, ?, ?)''', ( str(random_day), random_name, str(random_id), random_hours, )) conn.commit() self.populate_record_data() self.populate_student_data() @pyqtSlot() def set_start_date(self): if datetime.strptime( self.lend.text(), "%a %b %d %Y") < datetime.strptime( self.my_calendar.selectedDate().toString(), "%a %b %d %Y"): self.lend.setText(self.my_calendar.selectedDate().toString()) self.lend.resize(self.lend.sizeHint()) self.lstart.setText(self.my_calendar.selectedDate().toString()) self.lstart.resize(self.lstart.sizeHint().width() + 6, self.lstart.sizeHint().height() + 3) @pyqtSlot() def set_end_date(self): if datetime.strptime(self.my_calendar.selectedDate().toString(), "%a %b %d %Y") < datetime.strptime( self.lstart.text(), "%a %b %d %Y"): date_error = QMessageBox() date_error.setIcon(QMessageBox.Critical) date_error.setInformativeText( 'End date cannot be before start date') date_error.setWindowTitle("Error: Date") date_error.exec_() else: self.lend.setText(self.my_calendar.selectedDate().toString()) self.lend.resize(self.lend.sizeHint().width() + 6, self.lend.sizeHint().height() + 3)
class Labels(QWidget): """ Attributes ---------- chan_name : list of str list of all the labels (with the user-defined changes) """ def __init__(self, parent): super().__init__() self.parent = parent self.filename = None self.chan_name = None # None when dataset is not loaded self.create() def create(self): self.idx_load = QPushButton('Load') self.idx_load.clicked.connect(self.load_labels) self.idx_load.setToolTip('Load file with a list of channels (separated by , or ; or tabs or spaces).') self.idx_save = QPushButton('Save') self.idx_save.clicked.connect(self.save_labels) self.idx_save.setEnabled(False) # cancel is equal to setting labels to what they were self.idx_cancel = QPushButton('Cancel') self.idx_cancel.clicked.connect(self.update) self.idx_apply = QPushButton('Apply') self.idx_apply.clicked.connect(self.apply) self.idx_apply.setToolTip('Changes will take effect. This will reset the channel groups and traces.') layout_0 = QHBoxLayout() layout_0.addWidget(self.idx_load) layout_0.addWidget(self.idx_save) layout_1 = QHBoxLayout() layout_1.addWidget(self.idx_cancel) layout_1.addWidget(self.idx_apply) self.table = QTableWidget() self.table.horizontalHeader().setStretchLastSection(True) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setColumnCount(2) self.table.setHorizontalHeaderLabels(['Current Labels', 'New Labels']) self.table.cellChanged.connect(self.check_labels) layout = QVBoxLayout() layout.addLayout(layout_0) layout.addWidget(self.table) layout.addLayout(layout_1) self.setLayout(layout) self.setEnabled(False) def update(self, checked=False, labels=None, custom_labels=None): """Use this function when we make changes to the list of labels or when we load a new dataset. Parameters ---------- checked : bool argument from clicked.connect labels : list of str list of labels in the dataset (default) custom_labels : list of str list of labels from a file """ if labels is not None: self.setEnabled(True) self.chan_name = labels self.table.blockSignals(True) self.table.clearContents() self.table.setRowCount(len(self.chan_name)) for i, label in enumerate(self.chan_name): old_label = QTableWidgetItem(label) old_label.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) if custom_labels is not None and i < len(custom_labels) and custom_labels[i]: # it's not empty string or None label_txt = custom_labels[i] else: label_txt = label new_label = QTableWidgetItem(label_txt) self.table.setItem(i, 0, old_label) self.table.setItem(i, 1, new_label) self.table.blockSignals(False) def check_labels(self): # read new labels first labels = self._read_labels() # disable apply, if there are duplicates if len(labels) == len(set(labels)): self.idx_apply.setEnabled(True) else: self.idx_apply.setEnabled(False) # mark duplicates in red self.table.blockSignals(True) for i, label in enumerate(labels): if labels.count(label) > 1: self.table.item(i, 1).setBackground(QColor('red')) else: self.table.item(i, 1).setBackground(QColor('white')) self.table.blockSignals(False) def load_labels(self, checked=False, test_name=None): if self.filename is not None: filename = self.filename elif self.parent.info.filename is not None: filename = Path(self.parent.info.filename) else: filename = None if test_name: filename = test_name else: filename, _ = QFileDialog.getOpenFileName(self, 'Open Labels', str(filename.parent), 'Comma-separated values (*.csv);; Text file (*.txt);; All Files(*.*)') if filename == '': return self.filename = Path(filename) with self.filename.open() as f: text = f.read() labels = split(', |,|; |;|\t|\n| ',text) labels = [label.strip() for label in labels] self.update(custom_labels=labels) def save_labels(self): """Save labels modified by the user. TODO ---- Save labels modified by the user """ pass def apply(self): self.chan_name = self._read_labels() self.parent.info.dataset.header['chan_name'] = self.chan_name self.parent.channels.reset() self.parent.traces.reset() def reset(self): self.table.blockSignals(True) self.table.clearContents() self.table.blockSignals(False) self.setEnabled(False) def _read_labels(self): labels = [] for i in range(self.table.rowCount()): labels.append(self.table.item(i, 1).text()) return labels
class layer_widget(QWidget): changed = pyqtSignal() def combo_changed(self): self.save_model() self.emit_change() def tab_changed(self, x,y): self.save_model() self.emit_change() def emit_change(self): if self.optics_window!=False: if self.optics_window.isVisible()==True: self.optics_window.update() self.changed.emit() def sync_to_electrical_mesh(self): tot=0 for i in range(0,len(self.model)): if yes_no(self.model[i][COLUMN_DEVICE])==True: tot=tot+float(self.model[i][COLUMN_THICKNES]) lines=[] if inp_load_file(lines,os.path.join(os.getcwd(),"mesh_y.inp"))==True: mesh_layers=int(inp_search_token_value(lines, "#mesh_layers")) if mesh_layers==1: inp_update_token_value(os.path.join(os.getcwd(),"mesh_y.inp"), "#mesh_layer_length0", str(tot),1) def layer_type_edit(self): for i in range(0,self.tab.rowCount()): if tab_get_value(self.tab,i,3).lower()=="active layer" and tab_get_value(self.tab,i,4).startswith("dos")==False: tab_set_value(self.tab,i,4,epitay_get_next_dos()) tab_set_value(self.tab,i,5,epitay_get_next_pl()) mat_dir=os.path.join(get_materials_path(),tab_get_value(self.tab,i,2)) new_file=tab_get_value(self.tab,i,4)+".inp" if inp_isfile(new_file)==False: inp_copy_file(new_file,os.path.join(mat_dir,"dos.inp")) new_file=tab_get_value(self.tab,i,5)+".inp" if inp_isfile(new_file)==False: inp_copy_file(new_file,os.path.join(mat_dir,"pl.inp")) if tab_get_value(self.tab,i,3).lower()!="active layer" and tab_get_value(self.tab,i,4).startswith("dos")==True: tab_set_value(self.tab,i,4,tab_get_value(self.tab,i,3)) tab_set_value(self.tab,i,5,"none") self.save_model() self.emit_change() def rebuild_mat_list(self): self.material_files=[] mat=find_materials() for i in range(0,len(mat)): self.material_files.append(mat[i]) scan_remove_file(os.path.join(get_materials_path(),mat[i])) scan_item_add(os.path.join("materials",mat[i],"fit.inp"),"#wavelength_shift_alpha","Absorption spectrum wavelength shift",1) scan_item_add(os.path.join("materials",mat[i],"fit.inp"),"#n_mul","Refractive index spectrum multiplier",1) scan_item_add(os.path.join("materials",mat[i],"fit.inp"),"#alpha_mul","Absorption spectrum multiplier",1) def callback_view_materials(self): dialog=gpvdm_open(get_materials_path()) dialog.show_inp_files=False ret=dialog.window.exec_() if ret==QDialog.Accepted: if os.path.isfile(os.path.join(dialog.get_filename(),"mat.inp"))==True: self.mat_window=materials_main(dialog.get_filename()) self.mat_window.show() else: plot_gen([dialog.get_filename()],[],"auto") def on_move_down(self): tab_move_down(self.tab) self.save_model() self.emit_change() def callback_optics_sim(self, widget, data=None): help_window().help_set_help(["optics.png",_("<big><b>The optical simulation window</b></big><br>Use this window to perform optical simulations. Click on the play button to run a simulation."),"play.png",_("Click on the play button to run an optical simulation. The results will be displayed in the tabs to the right.")]) if self.optics_window==False: self.optics_window=class_optical() if self.optics_window.isVisible()==True: self.optics_window.hide() else: self.optics_window.show() def __init__(self): QWidget.__init__(self) self.rebuild_mat_list() self.doping_window=False self.cost_window=False self.optics_window=False self.main_vbox=QVBoxLayout() self.toolbar=QToolBar() self.toolbar.setIconSize(QSize(32, 32)) self.tb_add = QAction(QIcon(os.path.join(get_image_file_path(),"add.png")), _("Add device layer"), self) self.tb_add.triggered.connect(self.on_add_item_clicked) self.toolbar.addAction(self.tb_add) self.tb_remove = QAction(QIcon(os.path.join(get_image_file_path(),"minus.png")), _("Delete device layer"), self) self.tb_remove.triggered.connect(self.on_remove_item_clicked) self.toolbar.addAction(self.tb_remove) self.tb_remove= QAction(QIcon(os.path.join(get_image_file_path(),"down.png")), _("Move device layer"), self) self.tb_remove.triggered.connect(self.on_move_down) self.toolbar.addAction(self.tb_remove) self.tb_doping = QAction(QIcon(os.path.join(get_image_file_path(),"doping.png")), _("Doping"), self) self.tb_doping.triggered.connect(self.callback_doping) self.toolbar.addAction(self.tb_doping) self.optics_button = QAction(QIcon(os.path.join(get_image_file_path(),"optics.png")), _("Optical simulation"), self) self.optics_button.triggered.connect(self.callback_optics_sim) self.toolbar.addAction(self.optics_button) self.tb_open = QAction(QIcon(os.path.join(get_image_file_path(),"organic_material.png")), _("Look at the materials database"), self) self.tb_open.triggered.connect(self.callback_view_materials) self.toolbar.addAction(self.tb_open) self.cost = QAction(QIcon(os.path.join(get_image_file_path(),"cost.png")), _("Calculate the cost of the solar cell"), self) self.cost.triggered.connect(self.callback_cost) self.toolbar.addAction(self.cost) self.main_vbox.addWidget(self.toolbar) self.tab = QTableWidget() self.tab.resizeColumnsToContents() self.tab.verticalHeader().setVisible(False) self.create_model() self.tab.cellChanged.connect(self.tab_changed) self.main_vbox.addWidget(self.tab) self.setLayout(self.main_vbox) def create_model(self): self.tab.clear() self.tab.setColumnCount(6) if enable_betafeatures()==False: self.tab.setColumnHidden(5, True) self.tab.setColumnHidden(4, True) self.tab.setSelectionBehavior(QAbstractItemView.SelectRows) self.tab.setHorizontalHeaderLabels([_("Layer name"), _("Thicknes"), _("Optical material"), _("Layer type"), _("DoS Layer"),_("PL Layer")]) self.tab.setRowCount(epitaxy_get_layers()) for i in range(0,epitaxy_get_layers()): thick=epitaxy_get_width(i) material=epitaxy_get_mat_file(i) dos_layer=epitaxy_get_electrical_layer(i) pl_file=epitaxy_get_pl_file(i) name=epitaxy_get_name(i) self.add_row(i,thick,material,dos_layer,pl_file,name) return def add_row(self,i,thick,material,dos_layer,pl_file,name): self.tab.blockSignals(True) dos_file="" if dos_layer.startswith("dos")==True: dos_file="active layer" else: dos_file=dos_layer item1 = QTableWidgetItem(str(name)) self.tab.setItem(i,0,item1) item2 = QTableWidgetItem(str(thick)) self.tab.setItem(i,1,item2) combobox = QComboBox() #combobox.setEditable(True) for a in self.material_files: combobox.addItem(str(a)) self.tab.setCellWidget(i,2, combobox) combobox.setCurrentIndex(combobox.findText(material)) p=combobox.palette() p.setColor(QPalette.Active, QPalette.Button, Qt.white); p.setColor(QPalette.Inactive, QPalette.Button, Qt.white); combobox.setPalette(p) #item3 = QTableWidgetItem(str(dos_file)) #self.tab.setItem(i,3,item3) combobox_layer_type = QComboBox() #combobox.setEditable(True) combobox_layer_type.addItem("contact") combobox_layer_type.addItem("active layer") combobox_layer_type.addItem("other") self.tab.setCellWidget(i,3, combobox_layer_type) combobox_layer_type.setCurrentIndex(combobox_layer_type.findText(str(dos_file).lower())) item3 = QTableWidgetItem(str(dos_layer)) self.tab.setItem(i,4,item3) item3 = QTableWidgetItem(str(pl_file)) self.tab.setItem(i,5,item3) scan_item_add("epitaxy.inp","#layer_material_file"+str(i),_("Material for ")+name,2) scan_item_add("epitaxy.inp","#layer_width"+str(i),_("Layer width ")+name,1) combobox.currentIndexChanged.connect(self.combo_changed) combobox_layer_type.currentIndexChanged.connect(self.layer_type_edit) self.tab.blockSignals(False) def clean_dos_files(self): files=inp_lsdir() tab=[] for i in range(0,self.tab.rowCount()): tab.append(str(tab_get_value(self.tab,i, 4))+".inp") for i in range(0,len(files)): if files[i].startswith("dos") and files[i].endswith(".inp"): disk_file=files[i] if disk_file not in tab: inp_remove_file(disk_file) def on_remove_item_clicked(self): tab_remove(self.tab) self.save_model() self.changed.emit() def change_active_layer_thickness(self,obj): thickness=obj.get_data("refresh") count=0 for item in self.model: if str2bool(item[COLUMN_DEVICE])==True: count=count+1 if count==1: for item in self.model: if str2bool(item[COLUMN_DEVICE])==True: item[COLUMN_THICKNES]=str(thickness) self.save_model() self.refresh(False) return def on_add_item_clicked(self): row=tab_insert_row(self.tab) self.add_row(row,"100e-9","pcbm","other","none","layer"+str(row)) self.save_model() self.changed.emit() def save_model(self): thick=[] mat_file=[] dos_file=[] pl_file=[] name=[] for i in range(0,self.tab.rowCount()): name.append(str(tab_get_value(self.tab,i, 0))) thick.append(str(tab_get_value(self.tab,i, 1))) mat_file.append(str(tab_get_value(self.tab,i, 2))) dos_file.append(str(tab_get_value(self.tab,i, 4))) pl_file.append(str(tab_get_value(self.tab,i, 5))) ret=epitaxy_load_from_arrays(name,thick,mat_file,dos_file,pl_file) if ret==False: error_dlg(self,_("Error in epitaxy, check the input values.")) epitaxy_save() self.clean_dos_files() #self.sync_to_electrical_mesh() def callback_doping(self): help_window().help_set_help(["doping.png",_("<big><b>Doping window</b></big>\nUse this window to add doping to the simulation")]) if self.doping_window==False: self.doping_window=doping_window() if self.doping_window.isVisible()==True: self.doping_window.hide() else: self.doping_window.show() def callback_cost(self): help_window().help_set_help(["cost.png",_("<big><b>Costs window</b></big>\nUse this window to calculate the cost of the solar cell and the energy payback time.")]) if self.cost_window==False: self.cost_window=cost() if self.cost_window.isVisible()==True: self.cost_window.hide() else: self.cost_window.show()
class CompositionGroupBox(QGroupBox): with importlib_resources.open_binary('LiquidDiffract.resources', 'pt_data.npy') as fp: _element_dict = np.load(fp, allow_pickle=True).item() def __init__(self, *args): super(CompositionGroupBox, self).__init__(*args) self.setTitle('Composition') self.setAlignment(Qt.AlignLeft) self.setStyleSheet( 'GroupBox::title{subcontrol-origin: margin; subcontrol-position: top left;}' ) self.create_widgets() self.style_widgets() self.create_layout() self.create_signals() def create_widgets(self): self.add_element_btn = QPushButton("Add") self.delete_element_btn = QPushButton("Delete") self.density_lbl = QLabel("Density:") self.density_input = QLineEdit("1.0") self.mass_density_label = QLabel('g/cm<sup>3</sup>') self.mass_density = QLineEdit('') self.composition_table = QTableWidget() def style_widgets(self): self.density_lbl.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.density_input.setAlignment(Qt.AlignRight) self.density_input.setValidator(QDoubleValidator()) self.density_input.setMaximumWidth(100) self.mass_density.setAlignment(Qt.AlignRight) self.mass_density.setMaximumWidth(100) self.mass_density.setReadOnly(True) self.mass_density.isReadOnly() self.composition_table.setColumnCount(4) self.composition_table.horizontalHeader().setVisible(True) self.composition_table.verticalHeader().setVisible(False) self.composition_table.setContentsMargins(0, 0, 0, 0) self.composition_table.horizontalHeader().setStretchLastSection(False) self.composition_table.setHorizontalHeaderLabels( ['Element', 'Z', 'Charge', 'n']) self.composition_table.horizontalHeaderItem(0).setToolTip( 'Atomic element ') self.composition_table.horizontalHeaderItem(1).setToolTip( 'Atomic number ') self.composition_table.horizontalHeaderItem(2).setToolTip('Charge ') self.composition_table.horizontalHeaderItem(3).setToolTip( 'Proportion of compound ') # Set the alignment to the headers self.composition_table.horizontalHeaderItem(0).setTextAlignment( Qt.AlignLeft) self.composition_table.horizontalHeaderItem(1).setTextAlignment( Qt.AlignHCenter) self.composition_table.horizontalHeaderItem(2).setTextAlignment( Qt.AlignHCenter) self.composition_table.horizontalHeaderItem(3).setTextAlignment( Qt.AlignHCenter) self.composition_table.setColumnWidth(0, 71) self.composition_table.setColumnWidth(1, 66) self.composition_table.setColumnWidth(2, 66) self.composition_table.setColumnWidth(3, 66) self.composition_table.setVerticalScrollBarPolicy( Qt.ScrollBarAlwaysOff) self.composition_table.horizontalHeader().setResizeMode( QHeaderView.Stretch) self.composition_table.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Minimum) self.composition_table.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.composition_table.setItemDelegate( utility.ValidatedItemDelegate(self)) def create_layout(self): self.main_layout = QVBoxLayout() self.main_layout.setContentsMargins(10, 10, 10, 7) self.main_layout.setSpacing(5) self.button_layout = QHBoxLayout() self.button_layout.setSpacing(15) self.button_layout.addWidget(self.add_element_btn) self.button_layout.addWidget(self.delete_element_btn) self.density_layout = QGridLayout() self.density_layout.addWidget(self.density_lbl, 0, 0) self.density_layout.addWidget(self.density_input, 0, 1) self.density_layout.addWidget(QLabel('atoms/A<sup>3</sup>'), 0, 2) self.density_layout.addWidget(self.mass_density, 1, 1) self.density_layout.addWidget(self.mass_density_label, 1, 2) self.main_layout.addLayout(self.button_layout) self.main_layout.addWidget(self.composition_table) self.main_layout.addLayout(self.density_layout) self.setLayout(self.main_layout) def create_signals(self): self.delete_element_btn.clicked.connect(self.delete_row) self.add_element_btn.clicked.connect(self.add_row) self.density_input.textChanged.connect(self.update_mass_density) self.composition_table.cellChanged.connect(self.update_mass_density) def add_row(self): _row_position = self.composition_table.rowCount() self.composition_table.insertRow(_row_position) _element_editor = QComboBox() _element_editor.setStyleSheet('QComboBox {border: 0px ;} ') for index, element in enumerate(CompositionGroupBox._element_dict): _element_editor.insertItem(index, element) # Could use a default style? e.g.: # _element_editor.setStyle(QStyleFactory.create('Cleanlooks')) # Block composition_table signals while setting the cells self.composition_table.blockSignals(True) self.composition_table.setCellWidget(_row_position, 0, _element_editor) _element_editor.setCurrentIndex(30) # No need to set default here # self.composition_table.setItem(_row_position , 0, QTableWidgetItem('Ga')) self.composition_table.setItem(_row_position, 1, QTableWidgetItem('31')) self.composition_table.setItem(_row_position, 2, QTableWidgetItem('0')) self.composition_table.setItem(_row_position, 3, QTableWidgetItem('1')) # Re-enable signals self.composition_table.blockSignals(False) self.composition_table.item( _row_position, 1).setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.composition_table.item( _row_position, 1).setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) self.composition_table.item(_row_position, 1).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.composition_table.item(_row_position, 2).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.composition_table.item(_row_position, 3).setTextAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # Create Signal _element_editor.currentIndexChanged.connect(self.update_cb_val) self.update_mass_density() def delete_row(self): # Selects last row if none selected by user _selected_row = self.composition_table.currentRow() if _selected_row == -1: _selected_row = self.composition_table.rowCount() - 1 else: pass self.composition_table.removeRow(_selected_row) self.update_mass_density() def update_cb_val(self): _cb_widget = self.sender() _current_row = self.composition_table.indexAt(_cb_widget.pos()).row() _new_element = str(_cb_widget.currentText()) _new_Z_val = str(CompositionGroupBox._element_dict[_new_element]) self.composition_table.item(_current_row, 1).setText(_new_Z_val) self.update_mass_density() def get_composition_dict(self): '''Return composition dictionary''' # Form of dictionary is col 0 is key, val is tuple(col1,col2,col3) _composition_dict = {} _key_list = list(CompositionGroupBox._element_dict.keys()) _val_list = list(CompositionGroupBox._element_dict.values()) for _row_index in range(self.composition_table.rowCount()): _Z = np.int(self.composition_table.item(_row_index, 1).text()) _charge = np.int(self.composition_table.item(_row_index, 2).text()) _n = np.int(self.composition_table.item(_row_index, 3).text()) _key = str(_key_list[_val_list.index(_Z)]) _dict_entry = {_key: [_Z, _charge, _n]} _composition_dict.update(_dict_entry) # This code snippet convert _n from integer number of atoms in the # formula unit to a fraction of the total - e.g. SiO2 from Si=1 >> 1/3 # _n_total = sum([_composition_dict[_el][2] for _el in _composition_dict]) # for _el in _composition_dict: # _composition_dict[_el][2] /= _n_total # _composition_dict[_el] = tuple(_composition_dict[_el]) # print(_composition_dict) return _composition_dict def update_mass_density(self): _composition = self.get_composition_dict() # Handle errors caused by QDoubleValidator allowing intermediate # values to pass e.g. '.', ' ' etc. try: _atomic_density = np.float(self.density_input.text()) except ValueError: _atomic_density = 0.0 _mass_density = core.conv_density(_atomic_density, _composition) self.mass_density.setText('{0:.3f}'.format(_mass_density))
class HexEditor(QWidget): def __init__(self): QWidget.__init__(self) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.data = bytearray() self.datatable = QTableWidget() self.datatable.cellChanged.connect(self._cell_changed) self.datatable.horizontalHeader().setStretchLastSection(True) self.row_size = 16 self.read_only = False self.redraw_table() self.layout().addWidget(self.datatable) def set_bytes(self, bs): self.data = bytearray(bs) self.redraw_table() def get_bytes(self): return bytes(self.data) def setReadOnly(self, ro): self.read_only = ro self.redraw_table() def _redraw_strcol(self, row): start = self.row_size * row end = start + self.row_size data = self.data[start:end] print_data = printable_data(data, include_newline=False) item = QTableWidgetItem(print_data) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.datatable.setItem(row, self.str_col, item) def redraw_table(self, length=None): with DisableUpdates(self.datatable): oldsig = self.datatable.blockSignals(True) self.row_size = length or self.row_size self.datatable.setColumnCount(self.row_size + 1) self.datatable.setRowCount(0) self.str_col = self.row_size self.datatable.horizontalHeader().hide() self.datatable.verticalHeader().hide() rows = int(len(self.data) / self.row_size) if len(self.data) % self.row_size > 0: rows += 1 self.datatable.setRowCount(rows) for i in range(rows * self.row_size): row = i / self.row_size col = i % self.row_size if i < len(self.data): dataval = "%02x" % self.data[i] item = QTableWidgetItem(dataval) if self.read_only: item.setFlags(item.flags() ^ Qt.ItemIsEditable) else: item = QTableWidgetItem("") item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.datatable.setItem(row, col, item) for row in range(rows): self._redraw_strcol(row) self.datatable.blockSignals(oldsig) self.datatable.resizeColumnsToContents() self.datatable.resizeRowsToContents() @classmethod def _format_hex(cls, n): return ("%02x" % n).upper() @pyqtSlot(int, int) def _cell_changed(self, row, col): oldsig = self.datatable.blockSignals(True) if col == self.str_col: return if len(self.data) == 0: return data_ind = self.row_size * row + col if data_ind >= len(self.data): return data_text = self.datatable.item(row, col).text() try: data_val = int(data_text, 16) if data_val < 0x0 or data_val > 0xff: raise Exception() except Exception as e: item = QTableWidgetItem(self._format_hex(self.data[data_ind])) self.datatable.setItem(row, col, item) self.datatable.blockSignals(oldsig) return if data_text != self._format_hex(data_val): self.datatable.setItem( row, col, QTableWidgetItem(self._format_hex(data_val))) self.data[data_ind] = data_val self._redraw_strcol(row) self.datatable.blockSignals(oldsig)
class update_window(QWidget): got_updates = pyqtSignal() def __init__(self): QWidget.__init__(self) self.setMinimumWidth(1000) self.vbox=QVBoxLayout() self.setWindowTitle("Download updates (https://www.gpvdm.com)") toolbar=QToolBar() toolbar.setToolButtonStyle( Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(42, 42)) self.tb_update = QAction(icon_get("update"), _("Download updates"), self) self.tb_update.triggered.connect(self.download_updates) toolbar.addAction(self.tb_update) self.vbox.addWidget(toolbar) self.tab = QTableWidget() self.tab.resizeColumnsToContents() self.tab.verticalHeader().setVisible(False) self.tab.setColumnCount(5) self.tab.setSelectionBehavior(QAbstractItemView.SelectRows) #self.tab.setColumnWidth(3, 200) self.tab.verticalHeader().setVisible(False) #self.select_param_window=select_param(self.tab) #self.select_param_window.set_save_function(self.save_combo) #self.create_model() #self.tab.cellChanged.connect(self.tab_changed) self.vbox.addWidget(self.tab) self.status_bar=QStatusBar() self.vbox.addWidget(self.status_bar) self.setLayout(self.vbox) self.update=update_cache("materials") self.show_updates() self.got_updates.connect(self.show_updates) self.update_check() def show_updates(self): self.tab.blockSignals(True) self.tab.clear() self.tab.setRowCount(0) self.tab.setColumnWidth(0, 300) self.tab.setColumnWidth(1, 300) self.tab.setHorizontalHeaderLabels([_("File"),_("Description"), _("Size"), _("md5"), _("status")]) for i in range(0,len(self.update.file_list)): pos = self.tab.rowCount() self.tab.insertRow(pos) self.tab.setItem(pos,0,QTableWidgetItem(self.update.file_list[i].file_name)) self.tab.setItem(pos,1,QTableWidgetItem(str(self.update.file_list[i].description))) self.tab.setItem(pos,2,QTableWidgetItem(str(self.update.file_list[i].size))) self.tab.setItem(pos,3,QTableWidgetItem(str(self.update.file_list[i].md5))) self.tab.setItem(pos,4,QTableWidgetItem(str(self.update.file_list[i].status))) self.tab.blockSignals(False) def thread_get_updates(self): self.status_bar.showMessage("Checking for updates.....") self.update.updates_get() self.status_bar.showMessage("Done..") self.got_updates.emit() def thread_download_updates(self): self.status_bar.showMessage("Downloading updates.....") self.update.updates_get() self.update.updates_download() self.update.updates_install(get_materials_path()) self.status_bar.showMessage("Done..") self.got_updates.emit() def update_check(self): p = Thread(target=self.thread_get_updates) p.daemon = True p.start() def download_updates(self): p = Thread(target=self.thread_download_updates) p.daemon = True p.start()
class layer_widget(QWidgetSavePos): def combo_changed(self): self.save_model() self.emit_change() self.emit_structure_changed() def callback_tab_selection_changed(self): #self.tab_changed(0,0) self.emit_change() def tab_changed(self, x, y): self.save_model() self.emit_structure_changed() def emit_change(self): global_object_run("gl_force_redraw") def emit_structure_changed( self): #This will emit when there has been an edit global_object_run("mesh_update") global_object_run("optics_force_redraw") global_object_run("gl_force_redraw") def layer_type_edit(self): for i in range(0, self.tab.rowCount()): if tab_get_value(self.tab, i, 3).lower() == "active layer" and tab_get_value( self.tab, i, 4).startswith("dos") == False: tab_set_value(self.tab, i, 4, epitay_get_next_dos()) tab_set_value(self.tab, i, 5, epitay_get_next_pl()) mat_dir = os.path.join(get_materials_path(), tab_get_value(self.tab, i, 2)) new_file = tab_get_value(self.tab, i, 4) + ".inp" if inp_isfile(new_file) == False: dos_path = os.path.join(mat_dir, "dos.inp") if os.path.isfile(dos_path) == False: dos_path = os.path.join(get_default_material_path(), "dos.inp") inp_copy_file(new_file, dos_path) new_file = tab_get_value(self.tab, i, 5) + ".inp" if inp_isfile(new_file) == False: inp_copy_file(new_file, os.path.join(mat_dir, "pl.inp")) if tab_get_value(self.tab, i, 3).lower() != "active layer" and tab_get_value( self.tab, i, 4).startswith("dos") == True: tab_set_value(self.tab, i, 4, tab_get_value(self.tab, i, 3)) tab_set_value(self.tab, i, 5, "none") if tab_get_value(self.tab, i, 3).lower() == "other": tab_set_value(self.tab, i, 4, tab_get_value(self.tab, i, 3)) if tab_get_value(self.tab, i, 3).lower() == "contact": tab_set_value(self.tab, i, 4, tab_get_value(self.tab, i, 3)) self.save_model() self.emit_change() global_object_run("dos_update") global_object_run("pl_update") def on_move_down(self): tab_move_down(self.tab) self.save_model() self.emit_change() self.emit_structure_changed() def on_move_up(self): tab_move_up(self.tab) self.save_model() self.emit_change() self.emit_structure_changed() def __init__(self): QWidgetSavePos.__init__(self, "layer_widget") self.setWindowTitle(_("Layer editor") + " https://www.gpvdm.com") self.setWindowIcon(icon_get("layers")) self.resize(800, 500) self.cost_window = False self.main_vbox = QVBoxLayout() self.toolbar = QToolBar() self.toolbar.setIconSize(QSize(32, 32)) self.tb_add = QAction(icon_get("list-add"), _("Add device layer"), self) self.tb_add.triggered.connect(self.on_add_item_clicked) self.toolbar.addAction(self.tb_add) self.tb_remove = QAction(icon_get("list-remove"), _("Delete device layer"), self) self.tb_remove.triggered.connect(self.on_remove_item_clicked) self.toolbar.addAction(self.tb_remove) self.tb_down = QAction(icon_get("go-down"), _("Move device layer"), self) self.tb_down.triggered.connect(self.on_move_down) self.toolbar.addAction(self.tb_down) self.tb_up = QAction(icon_get("go-up"), _("Move device layer"), self) self.tb_up.triggered.connect(self.on_move_up) self.toolbar.addAction(self.tb_up) self.main_vbox.addWidget(self.toolbar) self.tab = QTableWidget() #self.tab.resizeColumnsToContents() self.tab.verticalHeader().setVisible(False) self.create_model() self.tab.cellChanged.connect(self.tab_changed) self.tab.itemSelectionChanged.connect( self.callback_tab_selection_changed) self.main_vbox.addWidget(self.tab) self.setLayout(self.main_vbox) self.tab.itemSelectionChanged.connect(self.layer_selection_changed) def create_model(self): self.tab.clear() self.tab.setColumnCount(6) if enable_betafeatures() == False: self.tab.setColumnHidden(5, True) self.tab.setColumnHidden(4, True) self.tab.setSelectionBehavior(QAbstractItemView.SelectRows) self.tab.setHorizontalHeaderLabels([ _("Layer name"), _("Thicknes"), _("Optical material"), _("Layer type"), _("DoS Layer"), _("PL Layer") ]) self.tab.setColumnWidth(2, 250) self.tab.setRowCount(epitaxy_get_layers()) for i in range(0, epitaxy_get_layers()): thick = epitaxy_get_width(i) material = epitaxy_get_mat_file(i) dos_layer = epitaxy_get_electrical_layer(i) pl_file = epitaxy_get_pl_file(i) name = epitaxy_get_name(i) self.add_row(i, thick, material, dos_layer, pl_file, name) return def add_row(self, i, thick, material, dos_layer, pl_file, name): self.tab.blockSignals(True) dos_file = "" if dos_layer.startswith("dos") == True: dos_file = "active layer" else: dos_file = dos_layer item1 = QTableWidgetItem(str(name)) self.tab.setItem(i, 0, item1) item2 = QTableWidgetItem(str(thick)) self.tab.setItem(i, 1, item2) combobox = gpvdm_select() combobox.setText(material) combobox.button.clicked.connect(self.callback_material_select) self.tab.setCellWidget(i, 2, combobox) # combobox.setCurrentIndex(combobox.findText(material)) #p=combobox.palette() #p.setColor(QPalette.Active, QPalette.Button, Qt.white); #p.setColor(QPalette.Inactive, QPalette.Button, Qt.white); #combobox.setPalette(p) #item3 = QTableWidgetItem(str(dos_file)) #self.tab.setItem(i,3,item3) combobox_layer_type = QComboBoxLang() #combobox.setEditable(True) combobox_layer_type.addItemLang("contact", _("contact")) combobox_layer_type.addItemLang("active layer", _("active layer")) combobox_layer_type.addItemLang("other", _("other")) self.tab.setCellWidget(i, 3, combobox_layer_type) combobox_layer_type.setValue_using_english(str(dos_file).lower()) item3 = QTableWidgetItem(str(dos_layer)) self.tab.setItem(i, 4, item3) item3 = QTableWidgetItem(str(pl_file)) self.tab.setItem(i, 5, item3) #combobox.currentIndexChanged.connect(self.combo_changed) combobox_layer_type.currentIndexChanged.connect(self.layer_type_edit) self.tab.blockSignals(False) def callback_material_select(self): self.mat_select = materials_select() self.mat_select.init(self.tab) self.mat_select.set_save_function(self.combo_changed) self.mat_select.show() def clean_dos_files(self): files = inp_lsdir("sim.gpvdm") tab = [] for i in range(0, self.tab.rowCount()): tab.append(str(tab_get_value(self.tab, i, 4)) + ".inp") for i in range(0, len(files)): if files[i].startswith("dos") and files[i].endswith(".inp"): disk_file = files[i] if disk_file not in tab: inp_remove_file(disk_file) def on_remove_item_clicked(self): tab_remove(self.tab) self.save_model() self.emit_change() def change_active_layer_thickness(self, obj): thickness = obj.get_data("refresh") count = 0 for item in self.model: if str2bool(item[COLUMN_DEVICE]) == True: count = count + 1 if count == 1: for item in self.model: if str2bool(item[COLUMN_DEVICE]) == True: item[COLUMN_THICKNES] = str(thickness) self.save_model() self.refresh(False) return def on_add_item_clicked(self): row = tab_insert_row(self.tab) self.add_row(row, "100e-9", "pcbm", "other", "none", "layer" + str(row)) self.save_model() #self.changed.emit() self.emit_change() def save_model(self): thick = [] mat_file = [] dos_file = [] pl_file = [] name = [] for i in range(0, self.tab.rowCount()): name.append(str(tab_get_value(self.tab, i, 0))) thick.append(str(tab_get_value(self.tab, i, 1))) mat_file.append(str(tab_get_value(self.tab, i, 2))) dos_file.append(str(tab_get_value(self.tab, i, 4))) pl_file.append(str(tab_get_value(self.tab, i, 5))) ret = epitaxy_load_from_arrays(name, thick, mat_file, dos_file, pl_file) if ret == False: error_dlg(self, _("Error in epitaxy, check the input values.")) epitaxy_save(get_sim_path()) self.clean_dos_files() epitaxy_mesh_update() def layer_selection_changed(self): a = self.tab.selectionModel().selectedRows() if len(a) > 0: y = a[0].row() else: y = -1 if global_isobject("display_set_selected_layer") == True: global_object_get("display_set_selected_layer")(y) global_object_run("gl_force_redraw")
class update_window(QWidget): got_updates = pyqtSignal() def __init__(self): QWidget.__init__(self) self.setMinimumWidth(1000) self.setMinimumHeight(800) self.vbox = QVBoxLayout() self.setWindowTitle( _("Download extra materials") + " (https://www.gpvdm.com)") toolbar = QToolBar() toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QSize(42, 42)) self.tb_update = QAction_lock("update", _("Download extra\nmaterials"), self, "update") self.tb_update.clicked.connect(self.download_updates) toolbar.addAction(self.tb_update) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) toolbar.addWidget(spacer) #self.progress=progress_class() #self.progress.spinner.stop() #self.progress.spinner.hide() #self.progress.set_text(_("Connecting to server")) #self.progress.hide_time() #toolbar.addWidget(self.progress) self.vbox.addWidget(toolbar) self.tab = QTableWidget() self.tab.resizeColumnsToContents() self.tab.verticalHeader().setVisible(False) self.tab.setColumnCount(6) self.tab.setSelectionBehavior(QAbstractItemView.SelectRows) #self.tab.setColumnWidth(3, 200) self.tab.verticalHeader().setVisible(False) #self.select_param_window=select_param(self.tab) #self.select_param_window.set_save_function(self.save_combo) #self.create_model() #self.tab.cellChanged.connect(self.tab_changed) self.vbox.addWidget(self.tab) self.status_bar = QStatusBar() self.vbox.addWidget(self.status_bar) self.setLayout(self.vbox) self.update = update_cache() self.show_updates() self.update.update_progress.connect(self.update_progress) self.got_updates.connect(self.show_updates) self.update_check() self.setWindowIcon(icon_get("update")) self.timer = QTimer() self.timer.setSingleShot(False) self.timer.timeout.connect(self.update_ui) self.timer.start(500) def update_progress(self, line, percent): if self.isVisible() == True: if line != -1: self.tab.setItem( line, 5, QTableWidgetItem( str(self.update.file_list[line].get_status()))) self.tab.setItem( line, 4, QTableWidgetItem(str( self.update.file_list[line].md5_disk))) self.tab.selectRow(line) #self.progress.set_fraction(percent) #self.progress.set_text(self.update.get_progress_text()) process_events() def update_ui(self): if self.isVisible() == True: if self.update.updates_avaliable() == True: self.tb_update.setEnabled(True) else: self.tb_update.setEnabled(False) #print("write") def show_updates(self): self.tab.blockSignals(True) self.tab.clear() self.tab.setRowCount(0) self.tab.setColumnWidth(0, 50) self.tab.setColumnWidth(1, 200) self.tab.setColumnWidth(2, 200) self.tab.setColumnWidth(5, 300) self.tab.setHorizontalHeaderLabels([ _("ID"), _("File"), _("Description"), _("Size"), _("md5"), _("status") ]) for i in range(0, len(self.update.file_list)): pos = self.tab.rowCount() self.tab.insertRow(pos) self.tab.setItem(pos, 0, QTableWidgetItem(str(i))) self.tab.setItem( pos, 1, QTableWidgetItem(self.update.file_list[i].file_name)) self.tab.setItem( pos, 2, QTableWidgetItem(str(self.update.file_list[i].text))) self.tab.setItem( pos, 3, QTableWidgetItem(sizeof_fmt(self.update.file_list[i].size))) self.tab.setItem( pos, 4, QTableWidgetItem(str(self.update.file_list[i].md5_disk))) self.tab.setItem( pos, 5, QTableWidgetItem(str(self.update.file_list[i].get_status()))) self.tab.setItem( pos, 6, QTableWidgetItem(str(self.update.file_list[i].md5_disk))) #self.tab.setItem(pos,4,QTableWidgetItem(str(self.update.file_list[i].get_status()))) #self.tab.setItem(pos,5,QTableWidgetItem(str(self.update.file_list[i].ver))) self.tab.blockSignals(False) self.status_bar.showMessage("") def thread_get_updates(self): self.update.updates_get() self.got_updates.emit() def thread_download_updates(self): self.update.updates_get() self.update.updates_download() self.update.updates_install() self.got_updates.emit() def update_check(self): self.status_bar.showMessage("Checking for updates.....") self.update_check_thread = Thread(target=self.thread_get_updates) self.update_check_thread.daemon = True self.update_check_thread.start() def download_updates(self): if get_lock().is_trial() == True: self.trial = trial( override_text= "<br><br><br><br>Please buy gpvdm to get access to the full materials database.<br><br><br>", show_text=False, title_font_size=14) self.trial.title_text.setAlignment(Qt.AlignCenter) ret = self.trial.run() if ret == QDialog.Accepted: msgBox = msg_dlg() msgBox.setText("Thank you for buying gpvdm") reply = msgBox.exec_() return self.status_bar.showMessage("Downloading updates.....") p = Thread(target=self.thread_download_updates) p.daemon = True p.start()
class AdvSetTab(QWidget): def __init__(self, gui): super(AdvSetTab, self).__init__() self._ui = gui self._config = self._ui.config self._tran = self._ui.tran self._parser = self._ui.configparser self.setObjectName("advtab") # main layout of this tab self._layout = QGridLayout(self) self._layout.setObjectName('advtab_layout') # setup the new button self._button_new = QPushButton(self) self._button_new.setObjectName('advtab_button_new') self._button_new.setText( self._tran.get_text(self._button_new.objectName())) self._button_new.clicked.connect(self._new_setting) # setup the delete button self._button_del = QPushButton(self) self._button_del.setObjectName('advtab_button_delete') self._button_del.setText( self._tran.get_text(self._button_del.objectName())) self._button_del.clicked.connect(self._delete_setting) # setup the delete all self._button_del_all = QPushButton(self) self._button_del_all.setObjectName('advtab_button_delete_all') self._button_del_all.setText( self._tran.get_text(self._button_del_all.objectName())) self._button_del_all.clicked.connect(self._delete_config) # setup the reset all button self._button_res_all = QPushButton(self) self._button_res_all.setObjectName('advtab_button_reset_all') self._button_res_all.setText( self._tran.get_text(self._button_res_all.objectName())) self._button_res_all.clicked.connect(self._reset_config) # setup the option table self._table = QTableWidget(0, 2) self._table.setObjectName('advtab_table') self._table.setHorizontalHeaderLabels( [self._tran.get_text('option'), self._tran.get_text('value')]) self._table.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self._table.setSelectionBehavior(QAbstractItemView.SelectRows) self._table.setSelectionMode(QAbstractItemView.SingleSelection) self._table.setShowGrid(False) self._table.setGeometry(0, 0, 800, 400) self._table.itemChanged.connect(self._changed_setting) # add the elements to the top level layout of the tab self._layout.addWidget(self._button_new, 0, 0, 1, 1) self._layout.addWidget(self._button_del, 0, 1, 1, 1) self._layout.addWidget(self._button_del_all, 0, 2, 1, 1) self._layout.addWidget(self._button_res_all, 0, 3, 1, 1) self._layout.addWidget(self._table, 1, 0, 4, 4) # set the array with all elements that have tooltips self._tooltips = [ self._button_new, self._button_del, self._button_del_all, self._button_res_all, ] def _changed_setting(self): """ Update the config with the values from the ui :return: """ # run through the table for a in range(self._table.rowCount()): # get key and value of the current row _key = self._table.item(a, 0).text() if self._table.item(a, 0) else "" _value = self._table.item(a, 1).text() if self._table.item( a, 1) else "" # update the config self._config[_key] = _value # make the default config visible in the ui, toggle tooltips, ... self.init() def _new_setting(self): """ Add a new setting to the config :return: None """ # get the number of rows in the table _rows = self._table.rowCount() # create a wizard to get the setting name and setting value from the user _wizard = OptionWizard(self._tran.get_text('option_wizard_title'), self._tran) # open the wizard _wizard.exec_() # only if the user has entered a setting name if _wizard.setting_name(): # create a new item holding the entered data self._table.insertRow(_rows) _key = QTableWidgetItem(_wizard.setting_name()) # make the new item editable and selectable _flags = _key.flags() _flags |= Qt.ItemIsSelectable _flags &= Qt.ItemIsEditable _key.setFlags(_flags) # add the new item to the table self._table.setItem(_rows, 0, _key) self._table.setItem(_rows, 1, QTableWidgetItem(_wizard.setting_value())) # add the new setting to the config self._config[_wizard.setting_name()] = _wizard.setting_value() # make the default config visible in the ui, toggle tooltips, ... self.init() def _delete_setting(self): """ Remove the selected setting from config :return: None """ # get the index of the selected row try: _row = self._table.selectedItems()[0].row() except KeyError: return # delete the options from config del self._config[self._table.selectedItems()[0].text()] # remove the row from the table self._table.removeRow(_row) # make the default config visible in the ui, toggle tooltips, ... self.init() def _delete_config(self): """ Remove all setting from config :return: None """ # clear the config self.config = {} # make the default config visible in the ui, toggle tooltips, ... self.init() def _reset_config(self): """ Reset the config to default state :return: None """ # reset config to default self._config = cc_con.DEFAULT_CONFIG # make the default config visible in the ui, toggle tooltips, ... self.init() def toggle_tooltips(self, onoff): """ If "onoff" is either "True" or "true", then show tooltips for this tab, otherwise dont :param onoff: whether to show verbose tooltips :return: None """ if onoff == "True" or onoff == "true": # run through the list with elements that have tooltips... for ele in self._tooltips: # and activate the tooltip for each element ele.setToolTip( self._tran.get_text(ele.objectName() + '_tooltip')) else: # run through the list with elements that have tooltips... for ele in self._tooltips: # and disable the tooltip for each element ele.setToolTip("") def lock(self): """ Disable the ui components, so that the user cannot change settings while the program is running :return: None """ self._toggle_ui_components(False) def unlock(self): """ Enable the ui components, so that the user can change settings :return: None """ self._toggle_ui_components(True) def _toggle_ui_components(self, onoff): """ Enable/Disable ui components (DO NOT USE THIS DIRECTLY; use _lock()/_unlock()) :param onoff: True to enable, False to disable :return: None """ # enable/disable buttons self._button_del.setEnabled(onoff) self._button_del_all.setEnabled(onoff) self._button_new.setEnabled(onoff) self._button_res_all.setEnabled(onoff) # enable/disable editing of the items in the table for a in range(0, self._table.rowCount()): if onoff: self._table.setEnabled(True) else: self._table.setEnabled(False) def init(self): """ What to do, when this tab is activated: fill the table with the contents of the config, ... :return: None """ # prevent sending signals: otherwise this caused the itemChanged Signal of the table to fire which in turn # invokes this method, ... -> infinitely deep recursion self._table.blockSignals(True) # delete all rows from the table while self._table.rowCount() > 0: self._table.removeRow(0) # fill the table with the contents of the config _i = 0 for key, value in self._config.items(): self._table.insertRow(_i) self._table.setItem(_i, 0, QTableWidgetItem(key)) self._table.setItem(_i, 1, QTableWidgetItem(value)) _i += 1 # toggle tooltips if necessary if cc_con.SHOW_VERB_TOOL in self._config: self.toggle_tooltips(self._config[cc_con.SHOW_VERB_TOOL]) # if the options for showing tooltips has not been set they are shown per default else: self.toggle_tooltips("True") self._table.blockSignals(False) def config(self): """ Return the internal configuration of this tab :return: A dictionary containing the configuration """ return self._config
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__() self.setWindowTitle('Sign PDF GUI') self.author = kwargs['author'] self.version = kwargs['version'] self.cliApp = kwargs['cliApp'] self.title = 'Sign PDF GUI' cliAppBasename = os.path.splitext(self.cliApp)[0] scriptDir = os.path.dirname(os.path.realpath(__file__)) iconPath = os.path.sep.join([scriptDir, 'icon', cliAppBasename]) self.setWindowIcon(QIcon(iconPath)) self.listPdf = [] # List of pdfs self.listImage = [] # List of images if 'pdflist' in kwargs.keys() and len(kwargs['pdflist']) > 0: n = len(kwargs['pdflist']) for i in range(0, n): self.listPdf.append(os.path.realpath(kwargs['pdflist'][i])) self.left = 10 self.top = 10 self.width = 700 self.height = 480 self.setGeometry(self.left, self.top, self.width, self.height) self.btnWidth = 100 self.btnHeight = 30 self.selectAllWidth = 70 widget = QWidget() self.setCentralWidget(widget) self.vbox = QVBoxLayout() widget.setLayout(self.vbox) multiple = False threshold = 0.9 mask = "254,255,254,255,254,255" self.otherConfigs = { 'multiple': multiple, 'threshold': threshold, 'mask': mask } self.createPdfBox() self.createPdfTable() self.createImageBox() self.createImageTable() self.createButtonBox() self.createStatusBar() self.updatePdfTable() def createPdfBox(self): hbox = QHBoxLayout() self.vbox.addLayout(hbox) self.vbox.setAlignment(Qt.AlignTop) addPdfBtn = QPushButton("Add PDF", self) addPdfBtn.setFixedSize(self.btnWidth, self.btnHeight) addPdfBtn.clicked.connect(self.addPdfFiles) hbox.addWidget(addPdfBtn) delPdfBtn = QPushButton("Remove PDF", self) delPdfBtn.setFixedSize(self.btnWidth, self.btnHeight) delPdfBtn.clicked.connect(self.delPdfFiles) hbox.addWidget(delPdfBtn) hbox.addStretch(1) self.showGrid = QCheckBox("Show grid") self.showGrid.stateChanged.connect(self.showGridState) hbox.addWidget(self.showGrid) self.printCmd = QCheckBox("Print cmd") self.printCmd.stateChanged.connect(self.printCmdState) hbox.addWidget(self.printCmd) otherBtn = QPushButton("Others") otherBtn.setFixedSize(self.btnWidth, self.btnHeight) otherBtn.clicked.connect(self.otherBtn_clicked) hbox.addWidget(otherBtn) def createPdfTable(self): npdf = len(self.listPdf) self.pdfTable = QTableWidget() self.pdfTable.setRowCount(npdf) self.pdfTable.setColumnCount(2) self.pdfTable.setHorizontalHeaderLabels( "Select All;PDF Filename".split(";")) header = self.pdfTable.horizontalHeader() header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) header.resizeSection(0, self.selectAllWidth) self.vbox.setAlignment(Qt.AlignTop) # self.pdfTable.setMaximumHeight(self.tableMaxHeight) self.vbox.addWidget(self.pdfTable) # If header is clicked self.selectAllPdf = False header.sectionClicked.connect(self.onPdfTableHeaderClicked) def onPdfTableHeaderClicked(self, logicalIndex): n = len(self.listPdf) if logicalIndex == 0: # If the first row is clicked if not self.selectAllPdf: # If self.selectAllPdf is False self.selectAllPdf = True for r in range(0, n): self.pdfTable.cellWidget(r, 0).findChild(type( QCheckBox())).setChecked(True) else: self.selectAllPdf = False for r in range(0, n): self.pdfTable.cellWidget(r, 0).findChild(type( QCheckBox())).setChecked(False) def createImageBox(self): hbox = QHBoxLayout() self.vbox.addLayout(hbox) addImageBtn = QPushButton("Add Image") addImageBtn.setFixedSize(self.btnWidth, self.btnHeight) addImageBtn.clicked.connect(lambda x: self.addEditImage(False)) hbox.addWidget(addImageBtn) delImageBtn = QPushButton("Remove Image") delImageBtn.setFixedSize(self.btnWidth, self.btnHeight) delImageBtn.clicked.connect(self.delImageFiles) hbox.addWidget(delImageBtn) hbox.addStretch(1) def delImageFiles(self): # Clear the status message self.statusBar.clearMessage() self.selectAllImage = False n = len(self.listImage) for r in range(0, n): if self.imageTable.cellWidget(r, 0).findChild(type( QCheckBox())).isChecked(): # Remove the element from the list del self.listImage[r] # Insert blank in the list so that the number of # elements remain the same. self.listImage.insert(r, "") # Remove the blanks that had been inserted while ("" in self.listImage): self.listImage.remove("") # Update the table self.updateImageTable() return def createImageTable(self): n = len(self.listImage) self.imageTable = QTableWidget() self.imageTable.setRowCount(n) self.imageTable.setColumnCount(9) self.imageTable.setHorizontalHeaderLabels( "Select All;Edit;Image;Template;XValue;YValue;Scale;Page;Rotation". split(";")) header = self.imageTable.horizontalHeader() # header.setSectionResizeMode(1,QtWidgets.QHeaderView.Stretch) header.resizeSection(0, self.selectAllWidth) header.resizeSection(1, 46) self.vbox.setAlignment(Qt.AlignTop) # self.imageTable.setMaximumHeight(self.tableMaxHeight) self.vbox.addWidget(self.imageTable) # If header is clicked self.selectAllImage = False header.sectionClicked.connect(self.onImageTableHeaderClicked) self.imageTable.itemChanged.connect(self.itemChangedText) return # If cell value have changed, then update them on the dictionary def itemChangedText(self, item): row = item.row() col = item.column() # Get the 'key' = column header key = self.imageTable.horizontalHeaderItem(col).text().lower() # Get the 'value' = cell value from the table value = str(self.imageTable.item(row, col).text()) # Update the dictionary self.listImage[row].update_info(key, value) return # If image header has been clicked def onImageTableHeaderClicked(self, logicalIndex): n = len(self.listImage) if logicalIndex == 0: # If the first row is clicked if not self.selectAllImage: # If self.selectAllImage is False self.selectAllImage = True for r in range(0, n): self.imageTable.cellWidget(r, 0).findChild( type(QCheckBox())).setChecked(True) else: self.selectAllImage = False for r in range(0, n): self.imageTable.cellWidget(r, 0).findChild( type(QCheckBox())).setChecked(False) def updateImageTable(self): n = len(self.listImage) # Block the signals so that any editing doesn't call any signal self.imageTable.blockSignals(True) # Clears the table self.imageTable.setRowCount(0) # Create an empty list of 'Edit' buttons self.editImageBtn = [] for row in range(0, n): # Insert a new row self.imageTable.insertRow(row) # Insert a checkbox in the first column (col = 0) qw = QWidget() self.checkbox = QCheckBox() self.checkbox.setCheckState(Qt.Unchecked) tabhlayout = QHBoxLayout(qw) tabhlayout.addWidget(self.checkbox) tabhlayout.setAlignment(Qt.AlignCenter) tabhlayout.setContentsMargins(0, 0, 0, 0) self.imageTable.setCellWidget(row, 0, qw) # Insert a checkbox in the second column (col = 1) # Append to the list of 'Edit' buttons self.editImageBtn.append(QPushButton('Edit')) self.editImageBtn[row].setStyleSheet('margin:3px') self.imageTable.setCellWidget(row, 1, self.editImageBtn[row]) self.editImageBtn[row].clicked.connect( lambda x: self.addEditImage(True)) for col in range(2, 9): # Get the column header in lowercase colheader = self.imageTable.horizontalHeaderItem( col).text().lower() if colheader == 'image' or colheader == 'template': # If the column header is 'image' or 'template', then # get the file basename and also make the cells read-only itemString = os.path.basename( str(self.listImage[row].info[colheader])) item = QTableWidgetItem(itemString) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) else: # Just get the value from the dictionary itemString = str(self.listImage[row].info[colheader]) item = QTableWidgetItem(itemString) # Align and insert the value in the cell item.setTextAlignment(Qt.AlignCenter) self.imageTable.setItem(row, col, item) self.imageTable.blockSignals(False) # Add or edit the image info def addEditImage(self, editBool): # Clear the status message self.statusBar.clearMessage() dlg = ImageOptionsDialog(self) # If edit then if editBool: # Get the row number 'r' buttonClicked = self.sender() index = self.imageTable.indexAt(buttonClicked.pos()) r = index.row() # Get the r-th row dictionary imageInfo = self.listImage[r].get_dict() # Insert the image info in the dialog dlg.editInfoDialog(imageInfo) if dlg.exec_() == ImageOptionsDialog.Accepted: data = dlg.get_output() if editBool: # If edit, the replace the original content with # the new content self.listImage[r] = data['image'] self.listImage[r] = ImageOptions(data) else: # If not editing old info, then add it to the end self.listImage.append(data['image']) k = len(self.listImage) self.listImage[k - 1] = ImageOptions(data) # Update the image table self.updateImageTable() return def createButtonBox(self): hbox = QHBoxLayout() self.vbox.addLayout(hbox) aboutBtn = QPushButton("About") aboutBtn.setFixedSize(self.btnWidth, self.btnHeight) aboutBtn.clicked.connect(self.aboutBtn_clicked) hbox.addWidget(aboutBtn, alignment=Qt.AlignLeft) self.helpBtn = QPushButton("Help") self.helpBtn.setFixedSize(self.btnWidth, self.btnHeight) self.helpBtn.clicked.connect(self.helpBtn_clicked) hbox.addWidget(self.helpBtn) hbox.addStretch(1) self.applyBtn = QPushButton("Apply") self.applyBtn.setFixedSize(self.btnWidth, self.btnHeight) self.applyBtn.clicked.connect(self.applyBtn_clicked) hbox.addWidget(self.applyBtn) quitBtn = QPushButton("Quit") quitBtn.setFixedSize(self.btnWidth, self.btnHeight) quitBtn.clicked.connect(self.close) hbox.addWidget(quitBtn) return def aboutBtn_clicked(self): # Clear the status message self.statusBar.clearMessage() infoMsg = ('Version: ' + self.version + '\n' + 'Author: ' + self.author) dlg = MessageDialog(title=self.title + ': About', text='Sign PDF GUI', informativeText=infoMsg, icon=QStyle.SP_MessageBoxInformation) dlg.setMinimumWidth(300) dlg.exec_() return def helpBtn_clicked(self): cmd = self.cliApp + ' --help' self.proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE) stdout, stderr = self.proc.communicate() rtn = self.proc.returncode if rtn != 0: print("Problem getting help text.") return rtn msg = self.cliApp + ' --help' detailedMsg = stdout.decode("utf-8") dlg = MessageDialog( title=self.title + ': Help', icon=QStyle.SP_MessageBoxInformation, text=msg, detailedText=detailedMsg, ) dlg.setMinimumSize(550, 400) dlg.exec_() return def applyBtn_clicked(self): n = len(self.listPdf) if n == 0: dlg = MessageDialog(title=self.title + ': Error', text='Warning: No pdf file supplied.', icon=QStyle.SP_MessageBoxCritical) dlg.exec_() return self.pd = ProgressDialog() self.pd.setMinimum(0) self.pd.setMaximum(n) self.pd.setFormat("%v/%m") self.work = Worker(self) self.thread = Thread(target=self.work.run) self.thread.start() if self.pd.exec_() == ProgressDialog.Rejected: self.work.terminate() def createStatusBar(self): self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) return def addPdfFiles(self): # Clear the status message self.statusBar.clearMessage() options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog files, _ = (QFileDialog.getOpenFileNames( self, "QFileDialog.getOpenFileNames()", "", "PDF Files (*.pdf)", options=options)) if files: self.listPdf.extend(files) self.updatePdfTable() def updatePdfTable(self): n = len(self.listPdf) self.pdfTable.setRowCount(0) # Clear the table for r in range(0, n): self.pdfTable.insertRow(r) qw = QWidget() self.checkbox = QCheckBox() self.checkbox.setCheckState(Qt.Unchecked) tabhlayout = QHBoxLayout(qw) tabhlayout.addWidget(self.checkbox) tabhlayout.setAlignment(Qt.AlignCenter) tabhlayout.setContentsMargins(0, 0, 0, 0) self.pdfTable.setCellWidget(r, 0, qw) item = QTableWidgetItem(self.listPdf[r]) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.pdfTable.setItem(r, 1, item) def showGridState(self): self.statusBar.clearMessage() return def printCmdState(self): self.statusBar.clearMessage() return def delPdfFiles(self): # Clear the status message self.statusBar.clearMessage() self.selectAllPdf = False n = len(self.listPdf) for r in range(0, n): if self.pdfTable.cellWidget(r, 0).findChild(type( QCheckBox())).isChecked(): # Remove the element from the list del self.listPdf[r] # Insert blank in the list so that the number of # elements remain the same. self.listPdf.insert(r, "") # Remove the blanks that had been inserted while ("" in self.listPdf): self.listPdf.remove("") # Update the table self.updatePdfTable() return def otherBtn_clicked(self): dlg = OtherDialog(self.otherConfigs) if dlg.exec_() == OtherDialog.Accepted: self.otherConfigs = dlg.get_output() return
class mainwindow(QWidget): # set up signals module_selected = pyqtSignal() module_loaded = pyqtSignal() def __init__(self, parent=None): super(mainwindow, self).__init__(parent) # this is where we will store the acquisition module later self.m = None # set up splitter widget l = QVBoxLayout() self.setLayout(l) self.splitter = QSplitter(self) l.addWidget(self.splitter) # set up module interface widget scriptinterfacewidget = QWidget(self.splitter) self.txt_acquisition_script = QLineEdit('Path to acquistion script...') self.txt_acquisition_script.setReadOnly(True) self.btn_browse = QPushButton(QIcon('../icons/folder.png'), '') self.btn_browse.setToolTip('Browse') l1 = QHBoxLayout() for w in [self.txt_acquisition_script, self.btn_browse]: l1.addWidget(w) self.btn_load = QPushButton(QIcon('tools-wizard.png'), '') self.btn_load.setToolTip('Load module') self.btn_run = QPushButton(QIcon('../icons/start.png', ), '') self.btn_run.setToolTip('Run module') self.btn_stopmod = QPushButton(QIcon('../icons/stop.png'), '') self.btn_stopmod.setToolTip('Stop module') self.btn_forcestopmod = QPushButton(QIcon('../icons/kill.png'), '') self.btn_forcestopmod.setToolTip('Kill module') self.btn_help = QPushButton(QIcon('../icons/help.png'), '') self.btn_help.setToolTip('Show step-by-step help') for btn in [ self.btn_load, self.btn_run, self.btn_stopmod, self.btn_forcestopmod, self.btn_help ]: btn.setIconSize(QSize(32, 32)) for btn in [self.btn_run, self.btn_stopmod, self.btn_forcestopmod]: btn.setEnabled(False) l2 = QHBoxLayout() for w in [ self.btn_load, self.btn_run, self.btn_stopmod, self.btn_forcestopmod, self.btn_help ]: l2.addWidget(w) # set up parameters table self.lbl_params = QLabel('<b>Module parameters:</b>') self.tbl_params = QTableWidget() self.tbl_params.setColumnCount(2) self.tbl_params.setHorizontalHeaderLabels(['Name', 'Value']) self.tbl_params.verticalHeader().hide() self.tbl_params.horizontalHeader().setStretchLastSection(True) self.lbl_readonlyconsole = QLabel('<b>Terminal output:</b>') self.readonlyconsole = ReadOnlyConsole() l_siw = QVBoxLayout(scriptinterfacewidget) for l in [l1, l2]: l_siw.addLayout(l) for w in [ self.lbl_params, self.tbl_params, self.lbl_readonlyconsole, self.readonlyconsole ]: l_siw.addWidget(w) # set up plotter widget self.plotter = Plotter(self.splitter) # set up observables widget observablesinterfacewidget = QWidget( self.splitter ) # this widget will contain the observables list and the alarms # set up observables table self.lbl_observables = QLabel('<b>Observables:</b>', observablesinterfacewidget) self.tbl_observables = QTableWidget(observablesinterfacewidget) self.tbl_observables.setColumnCount(2) self.tbl_observables.setHorizontalHeaderLabels(['Name', 'Value']) self.tbl_observables.verticalHeader().hide() self.tbl_observables.horizontalHeader().setStretchLastSection(True) # set up alarms table self.lbl_alarm = QLabel('<b>Alarms:</b>', observablesinterfacewidget) self.tbl_alarms = QTableWidget(observablesinterfacewidget) self.tbl_alarms.setColumnCount(3) self.tbl_alarms.setHorizontalHeaderLabels( ['Condition', 'Action', 'Value']) self.tbl_alarms.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.tbl_alarms.verticalHeader().hide() self.btn_addalarm = QPushButton("Add alarm", observablesinterfacewidget) self.btn_addalarm.setEnabled(False) # put everything in a layout l = QVBoxLayout(observablesinterfacewidget) for w in [ self.lbl_observables, self.tbl_observables, self.lbl_alarm, self.tbl_alarms, self.btn_addalarm ]: l.addWidget(w) # make connections self.btn_browse.clicked.connect(self.browse_acquisition_script) self.btn_load.clicked.connect(self.load_module) self.btn_run.clicked.connect(self.run_module) self.btn_help.clicked.connect( lambda: self.update_step_help(reinit=True)) self.btn_addalarm.clicked.connect(self.add_alarm) self.tbl_alarms.cellChanged.connect(self.alarm_modified) # set window size. self.setWindowState(Qt.WindowMaximized) # set window title self.setWindowTitle("MAScriL - Mercury Acquisition Script Launcher") # set up step by step help self.lbl_step_help = QLabel(self) #self.lbl_step_help.setWindowFlag(Qt.ToolTip) self.lbl_step_help.setStyleSheet( 'QLabel {background-color: #FF6666; padding: 5px; padding-bottom: 15px;}' ) self.lbl_step_help.linkActivated.connect(self.on_step_help_link) self.step_help_count = 1 # set up the step help list: message, reference widget, relative position, signal for next step self.step_help = [ ('Select a module', self.btn_browse, 'bottomright', self.module_selected), ('Load the module', self.btn_load, 'bottomright', self.module_loaded), ('Set the parameters', self.tbl_params, 'bottomright', self.tbl_params.clicked), ('Select X and Y values for plotting', self.plotter.yvar, 'bottomright', self.plotter.yvar.clicked), ('Set up the alarms', self.lbl_alarm, 'topleft', self.tbl_alarms.clicked), ('Run the script', self.btn_run, 'bottomright', self.btn_run.clicked) ] def make_lambda(i): return lambda: self.next_step_help(check_step=i + 1, close_last=True) for i, step in enumerate(self.step_help): step[3].connect(make_lambda(i)) @pyqtSlot(QString) def on_step_help_link(self, str): if str == 'close': self.lbl_step_help.hide() self.step_help_count = 0 elif str == 'next': self.next_step_help() else: raise Exception('Unsupported link') @pyqtSlot() def next_step_help(self, check_step=None, close_last=False): # check if we should update the step help if self.step_help_count == 0 or (check_step and check_step != self.step_help_count): return # update the step help self.step_help_count += 1 if self.step_help_count > len(self.step_help): if close_last: self.lbl_step_help.hide() self.step_help_count = 0 return else: self.step_help_count = 1 self.update_step_help() def update_step_help(self, reinit=False): if reinit: self.step_help_count = 1 title = 'Step ' + str(self.step_help_count) + ':' text = self.step_help[self.step_help_count - 1][0] widget = self.step_help[self.step_help_count - 1][1] point = self.step_help[self.step_help_count - 1][2] self.lbl_step_help.setText( '<span style="font-size: 16px; font-family: DejaVu Sans;"><b>↖</b>' + '<a href="close" style="text-decoration: none;">✖</a>' + '<a href="next" style="text-decoration: none;">➡</a>' + '</span>' + '<div style="margin-left: 10px; margin-right: 10px;">' + '<b>' + title + '</b><br>' + text + '</div>') self.lbl_step_help.adjustSize() if point == 'bottomright': self.lbl_step_help.move( widget.mapTo(self, QPoint(widget.width() - 5, widget.height() - 5))) elif point == 'topleft': self.lbl_step_help.move(widget.mapTo(self, QPoint(5, 5))) else: raise Exception("Invalid position") self.lbl_step_help.show() def paintEvent(self, event): super(mainwindow, self).paintEvent(event) if self.step_help_count > 0: if self.isActiveWindow(): self.update_step_help() else: self.lbl_step_help.hide() @pyqtSlot() def browse_acquisition_script(self): modulespath = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'modules') filename, filt = QFileDialog.getOpenFileName(self, 'Open File', directory=modulespath, filter='*.py') if filename: self.txt_acquisition_script.setText(filename) self.module_selected.emit() @pyqtSlot() def load_module(self): # check if there is a running module if isinstance(self.m, MeasurementBase) and self.m.isRunning(): QMessageBox.critical( self, "Error", "Cannot load a new module when the previous one is not done.") return # tidy up self.tbl_params.clearContents() self.plotter.clear() self.plotter.xvar.clear() self.plotter.yvar.clear() self.tbl_observables.clearContents() self.tbl_alarms.clearContents() for w in [ self.btn_run, self.btn_browse, self.btn_load, self.btn_help, self.btn_addalarm ]: w.setEnabled(False) self.tbl_alarms.blockSignals( True) # we will modify the alarms table, so disable signals # indicate we are loading self.btn_load.setIcon(QIcon('../icons/wait.png')) self.repaint() self.plotter.repaint() # check if we are dealing with a valid module module_loaded = False filename = str(self.txt_acquisition_script.text()) mod_name, file_ext = os.path.splitext(os.path.split(filename)[-1]) try: mod = imp.load_source(mod_name, filename) if not hasattr(mod, 'Measurement') or not issubclass( getattr(mod, 'Measurement'), MeasurementBase): QMessageBox.critical(self, "Error", "Could not get correct class from file.") else: module_loaded = True except IOError as e: QMessageBox.critical(self, "Error", "Could not load file: " + str(e.args[1])) except Exception as e: QMessageBox.critical( self, "Error", "Could not load module: " + traceback.format_exc()) if module_loaded: self.m = getattr(mod, 'Measurement')(redirect_console=True) # set up parameter table self.tbl_params.setRowCount(len(self.m.params)) for i, key in enumerate(self.m.params): item = QTableWidgetItem(key) item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.tbl_params.setItem(i, 0, item) value = self.m.params[key] if isinstance(value, list): value = '[' + ','.join(map(str, value)) + ']' elif isinstance(value, np.ndarray): value = '[' + ",".join(map(str, value.tolist())) + ']' if isinstance(value, MeasurementParameter): self.tbl_params.setCellWidget(i, 1, value.widget) value.mainwindow = self else: self.tbl_params.setItem(i, 1, QTableWidgetItem(str(value))) # set up plotter self.plotter.set_header(self.m.observables) # set up observables table self.tbl_observables.setRowCount(len(self.m.observables)) for i, label in enumerate(self.m.observables): for j in [0, 1]: item = QTableWidgetItem(label if j == 0 else '') item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.tbl_observables.setItem(i, j, item) # set up alarms table self.tbl_alarms.setRowCount(0) for i, alarm in enumerate(self.m.alarms): self.add_alarm( ) # this has the advantage of directly setting up the combobox as well self.tbl_alarms.item(i, 0).setText(self.m.alarms[i][0]) cmb = self.tbl_alarms.cellWidget(i, 1) cmb.setCurrentIndex(cmb.findData(self.m.alarms[i][1])) # connect signals self.m.new_observables_data[list].connect(self.new_data_handler) self.m.new_alarm_data[list].connect(self.new_alarm_data_handler) self.m.new_console_data[QString].connect( self.readonlyconsole.write) self.btn_stopmod.clicked.connect(self.m.quit) self.btn_stopmod.clicked.connect(self.quit_requested) self.btn_forcestopmod.clicked.connect(self.m.terminate) self.m.finished.connect(self.module_done) self.tbl_alarms.blockSignals(False) # activate run button and add alarm button for w in [self.btn_run, self.btn_addalarm]: w.setEnabled(True) self.module_loaded.emit() # activate some other buttons regardless of if the module was successfully loaded self.btn_load.setIcon(QIcon('tools-wizard.png')) for w in [self.btn_browse, self.btn_load, self.btn_help]: w.setEnabled(True) @pyqtSlot() def run_module(self): # check if no other module is running and if the module we loaded is valid if isinstance(self.m, MeasurementBase) and self.m.isRunning(): QMessageBox.critical( self, "Error", "Cannot run a new module when the previous one is not done.") return elif not isinstance(self.m, MeasurementBase): QMessageBox.critical(self, "Error", "Please load a valid module first.") return # deactivate request_quit flag in case process has been stopped previously # and disconnect all signals self.m.flags['quit_requested'] = False # update the parameters that do not update automatically # (i.e. that are not derived from MeasurementParameter) for i in range(self.tbl_params.rowCount()): key = str(self.tbl_params.item(i, 0).text()) if not isinstance(self.m.params[key], MeasurementParameter): # TODO: get rid of the python interpretation and cast value to the correct type (python evaluation should be a special kind of MeasurementParameter) value = self.tbl_params.item(i, 1).text() try: self.m.params[key] = eval(value, {'np': np}) except Exception as e: QMessageBox.critical( self, "Error", "Parameter '" + key + "' could not be evaluated: " + str(e.args[0])) return # disable parameter editing self.tbl_params.setEnabled(False) # update buttons self.btn_load.setEnabled(False) self.btn_run.setEnabled(False) self.btn_stopmod.setEnabled(True) # run the thread self.m.start() def quit_requested(self): self.btn_stopmod.setEnabled(False) self.btn_forcestopmod.setEnabled(True) def module_done(self): # enable parameter editing self.tbl_params.setEnabled(True) # update buttons self.btn_stopmod.setEnabled(False) self.btn_forcestopmod.setEnabled(False) self.btn_run.setEnabled(True) self.btn_load.setEnabled(True) @pyqtSlot() def add_alarm(self): # temporarily block the signals while we set up the new row in the alarms table blocked = self.tbl_alarms.signalsBlocked() self.tbl_alarms.blockSignals(True) # create a new row in the alarms table self.tbl_alarms.setRowCount(self.tbl_alarms.rowCount() + 1) i = self.tbl_alarms.rowCount() - 1 # create an editable field for the alarm condition self.tbl_alarms.setItem(i, 0, QTableWidgetItem()) # create a combo box for the alarm action cmb = QComboBox() for value, text in [(MeasurementBase.ALARM_SHOWVALUE, 'show value'), (MeasurementBase.ALARM_QUIT, 'stop acquisition'), (MeasurementBase.ALARM_CALLCOPS, 'call the cops')]: cmb.addItem(text, value) self.tbl_alarms.setCellWidget(i, 1, cmb) # create a non-editable field for the alarm info item = QTableWidgetItem() item.setFlags(item.flags() ^ Qt.ItemIsEditable) self.tbl_alarms.setItem(i, 2, item) # when the QComboBox is changed, we want to emit the cellChanged signal correspondingly cmb.currentIndexChanged.connect( lambda: self.tbl_alarms.cellChanged.emit(i, 1)) # unblock the signals, if they were previously unblocked self.tbl_alarms.blockSignals(blocked) @pyqtSlot(int, int) def alarm_modified(self, row, column): # update the alarms alarms = [] for i in range(self.tbl_alarms.rowCount()): condition = str(self.tbl_alarms.item(i, 0).text()) action = self.tbl_alarms.cellWidget(i, 1).currentData() alarms.append([condition, action]) self.m.alarms = alarms @pyqtSlot(list) def new_data_handler(self, data): for i, value in enumerate(data): self.tbl_observables.item(i, 1).setText(str(value)) self.plotter.new_data_handler(data) @pyqtSlot(list) def new_alarm_data_handler(self, data): for i, d in enumerate(data): action = self.tbl_alarms.cellWidget(i, 1).currentData() result_field = self.tbl_alarms.item(i, 2) self.tbl_alarms.item(i, 2).setBackground(Qt.white) if isinstance(d, Exception): result_field.setText('could not evaluate: ' + str(d)) continue if action == MeasurementBase.ALARM_SHOWVALUE: result_field.setText(str(d)) elif action == MeasurementBase.ALARM_QUIT: if data[i]: result_field.setText('stopping acquisition...') result_field.setBackground(Qt.red) elif action == MeasurementBase.ALARM_CALLCOPS: if data[i]: result_field.setText('calling the cops...') result_field.setBackground(Qt.red) else: result_field.setText('OK...') result_field.setBackground(Qt.green)
class OpticalTab(QWidget): """The Optical Tab of ErwinJr. This is designed to be a GUI wrapper of the class Stratum Member variable stratum: A class to describe the physics of 1D optical slab waveguide customized SIGNAL dirty: Show if there's any new changes to qclayers that need to be saved plotImag: bool, whether to plot the imaginary part of the refractive index redActive: bool, whether to plot the active part red --- GUI widget --- 1st column (settingBox): wlBox mtrlsBox rIdxRealBox rIdxImagBox periodBox repeatBox fieldBox gainCoeffBox facetBox[0] facetBox[1] refBox[0] refBox[1] ridgeLengthBox mirrorLoss(QLabel) 2nd column (strataBox): insertButton deleteButton strataTable solveButton OptimizeButton infoBox 3rd column (figureBox): optCanvas Member method setupActive """ dirty = pyqtSignal() def __init__(self, stratum=None, parent=None): super(OpticalTab, self).__init__(parent) self.stratum = stratum if stratum else OptStrata(3.0) self.periods = {} self.ridgeLength = 3.0 self.ridgeLoss = 0.0 self.Lps = {} self.facet = [facetList[0], facetList[0]] self.beta = None self.select = None self.redActive = False self.plotImag = True self.setAutoFillBackground(True) self.setBackgroundRole(QPalette.Window) if sys.platform.startswith('win'): settingBoxWidth = 200 strataBoxWidth = 350 elif sys.platform.startswith('darwin'): settingBoxWidth = 350 strataBoxWidth = 370 elif sys.platform.startswith('linux'): settingBoxWidth = 350 strataBoxWidth = 350 else: settingBoxWidth = 350 strataBoxWidth = 375 opticalLayout = QHBoxLayout() opticalLayout.setSpacing(0) opticalLayout.addLayout(self._settingBox(settingBoxWidth)) opticalLayout.addLayout(self._strataBox(strataBoxWidth)) figureBox = QVBoxLayout() self.optCanvas = EJcanvas(xlabel='Position $x$ (μm)', ylabel='Refractive index $n$') self.rIdxAxes = self.optCanvas.axes self.modeAxes = self.rIdxAxes.twinx() self.modeAxes.set_frame_on(False) self.modeAxes.get_yaxis().set_visible(False) # self.modeAxis = self.ridxAxis # self.modeAxis.set_ylabel("TM Mode $E_y$ (a.u.)", color='C0', # fontsize=canvasConfig["fontsize"]) # self.modeAxis.tick_params(axis='y', labelcolor='C0') figureBox.addWidget(self.optCanvas) opticalLayout.addLayout(figureBox) self.setLayout(opticalLayout) self.dirty.connect(self.update_xs) self.update_xs() self.update_Loss() def _settingBox(self, width): """Return a Qt Layout object containing all setting widgets""" settingBox = QVBoxLayout() settingBox.addWidget(QLabel('<b><center>Wavelength</center></b>')) self.wlBox = QDoubleSpinBox() self.wlBox.setDecimals(1) self.wlBox.setValue(self.stratum.wl) self.wlBox.setSuffix(' μm') self.wlBox.setRange(0.0, 100.0) self.wlBox.setMaximumWidth(width) settingBox.addWidget(self.wlBox) mtrlGroupBox = QGroupBox("Custom Material") mtrlGroupBox.setMaximumWidth(width) mtrlLayout = QGridLayout() mtrlLayout.addWidget(QLabel('<center>Material Name</center>'), 0, 0, 1, 2) self.mtrlsBox = QComboBox() self.mtrlsBox.addItems(self.stratum.cstmIndx.keys()) mtrlLayout.addWidget(self.mtrlsBox, 1, 0, 1, 2) mtrlLayout.addWidget(QLabel('<center>index n<sub>eff</sub></center>'), 2, 0) self.rIdxRealBox = QDoubleSpinBox() self.rIdxRealBox.setSingleStep(0.1) mtrlLayout.addWidget(self.rIdxRealBox, 3, 0) mtrlLayout.addWidget(QLabel('<center>passive loss k</center>'), 2, 1) self.rIdxImagBox = QDoubleSpinBox() self.rIdxImagBox.setSingleStep(0.1) mtrlLayout.addWidget(self.rIdxImagBox, 3, 1) mtrlLayout.addWidget(QLabel('<center>Period Length</center>'), 4, 0) self.periodBox = QDoubleSpinBox() self.periodBox.setSuffix(" Å") self.periodBox.setMaximum(19999.99) self.repeatBox = QSpinBox() self.repeatBox.setMaximum(999) mtrlLayout.addWidget(self.periodBox, 5, 0) mtrlLayout.addWidget(QLabel('<center>Periods</center>'), 4, 1) mtrlLayout.addWidget(self.repeatBox, 5, 1) mtrlLayout.addWidget(QLabel('<center>Active property</center>'), 6, 0, 1, 2) mtrlLayout.addWidget(QLabel('<center>Bias Field</center>'), 7, 0) self.fieldBox = QDoubleSpinBox() self.fieldBox.setSuffix(' kV/cm') self.fieldBox.setEnabled(False) self.fieldBox.setDecimals(1) self.fieldBox.setMaximum(500) mtrlLayout.addWidget(self.fieldBox, 8, 0) mtrlLayout.addWidget(QLabel('<center>Gain Coeff.</center>'), 7, 1) self.gainCoeffBox = QDoubleSpinBox() self.gainCoeffBox.setSuffix(' cm/kA') self.gainCoeffBox.setEnabled(False) self.gainCoeffBox.setDecimals(1) mtrlLayout.addWidget(self.gainCoeffBox, 8, 1) # TODO self.updateMtrl() mtrlLayout.setHorizontalSpacing(1) mtrlLayout.setVerticalSpacing(3) mtrlGroupBox.setLayout(mtrlLayout) settingBox.addWidget(mtrlGroupBox) self.wlBox.valueChanged[float].connect(self.input_wl) self.mtrlsBox.currentIndexChanged[int].connect(self.updateMtrl) self.rIdxRealBox.valueChanged[float].connect(self.input_rIdx) self.rIdxImagBox.valueChanged[float].connect(self.input_alpha) self.periodBox.valueChanged[float].connect(self.input_period) self.repeatBox.valueChanged[int].connect(self.input_repeats) self.gainCoeffBox.valueChanged[float].connect(self.input_gainCoeff) ridgeBox = QGroupBox("Ridge Geometry") ridgeBox.setMaximumWidth(width) ridgeLayout = QGridLayout() self.facetBox = [None] * 2 self.refBox = [None] * 2 for n in (0, 1): ridgeLayout.addWidget(QLabel("<center>Facet%d</center>" % (n + 1)), 0, n) self.facetBox[n] = QComboBox() self.facetBox[n].addItems(facetList) self.facetBox[n].setCurrentText(self.facet[n]) ridgeLayout.addWidget(self.facetBox[n], 1, n) self.refBox[n] = QDoubleSpinBox() self.refBox[n].setDecimals(1) self.refBox[n].setRange(0.0, 100.0) self.refBox[n].setSuffix(" %") self.refBox[n].setValue(self.facetRefct(n)) ridgeLayout.addWidget(self.refBox[n], 3, n) self.facetBox[n].currentIndexChanged[int].connect( partial(self.input_facet, n)) self.refBox[n].valueChanged[float].connect( partial(self.input_ref, n)) if self.facet[n] == "custom": self.refBox[n].setEnabled(True) self.refBox[n].blockSignals(False) else: self.refBox[n].setEnabled(False) self.refBox[n].blockSignals(True) ridgeLayout.addWidget(QLabel("<center>Reflectivity</center>"), 2, 0, 1, 2) ridgeLayout.addWidget(QLabel("<center>Ridge Length</center>"), 4, 0) self.ridgeLengthBox = QDoubleSpinBox() self.ridgeLengthBox.setDecimals(1) self.ridgeLengthBox.setRange(0.0, 20.0) self.ridgeLengthBox.setSingleStep(1) self.ridgeLengthBox.setValue(self.ridgeLength) self.ridgeLengthBox.setSuffix(" mm") self.ridgeLengthBox.valueChanged[float].connect(self.input_ridgeL) ridgeLayout.addWidget(self.ridgeLengthBox, 5, 0) ridgeLayout.addWidget(QLabel("<center>Mirror Loss: </center>"), 4, 1) self.mirrorLoss = QLabel("<center>0.0 cm<sup>-1</sup></center>") ridgeLayout.addWidget(self.mirrorLoss, 5, 1) ridgeLayout.setHorizontalSpacing(1) ridgeLayout.setVerticalSpacing(3) ridgeBox.setLayout(ridgeLayout) settingBox.addWidget(ridgeBox) settingBox.addStretch() return settingBox # _settingBox end def _strataBox(self, width): """Return a Qt Layout object containing all strata table widgets""" strataBox = QGridLayout() strataBox.setSpacing(5) self.insertButton = QPushButton("Insert Strata") self.insertButton.clicked.connect(self.insert_strata) self.deleteButton = QPushButton("Delete Strata") self.deleteButton.clicked.connect(self.delete_strata) strataBox.addWidget(self.insertButton, 0, 0) strataBox.addWidget(self.deleteButton, 0, 1) self.strataTable = QTableWidget() self.strataTable.setMaximumWidth(width) self.strataTable.setMinimumWidth(width) self.strataTable.setMinimumHeight(375) self.strataTable.setMaximumHeight(500) self.strataTable.setSelectionBehavior(QTableWidget.SelectRows) self.strataTable.setSelectionMode(QTableWidget.SingleSelection) self.strataTable.itemChanged.connect(self.strataTable_item) self.strataTable.itemSelectionChanged.connect(self.strataTable_select) self.strataTable_refresh() strataBox.addWidget(self.strataTable, 1, 0, 1, 2) self.solveButton = QPushButton("Solve Mode") self.solveButton.clicked.connect(self.solve) self.optimizeButton = QPushButton("Optimize Strata") self.optimizeButton.clicked.connect(self.optimizeStrata) self.optimizeButton.setEnabled(False) strataBox.addWidget(self.solveButton, 2, 0) strataBox.addWidget(self.optimizeButton, 2, 1) strataBox.addWidget(QLabel("Performance"), 3, 0, 1, 2) self.infoBox = QTextEdit() self.infoBox.setReadOnly(True) self.infoBox.setMinimumWidth(width) self.infoBox.setMaximumWidth(width) self.infoBox.setMinimumHeight(120) strataBox.addWidget(self.infoBox, 4, 0, 1, 2) strataBox.setRowStretch(5, 0) return strataBox @pyqtSlot(float) def input_wl(self, wl): """SLOT connected to self.wlBox.valueChanged[float]""" self.stratum.setWl(wl) self.dirty.emit() @pyqtSlot(int) def updateMtrl(self, idx=0): """SLOT connect to mtrlsBox.currentIndexChanged[int]""" if self.stratum.cstmIndx: self.mtrlsBox.setCurrentIndex(idx) mtrl = self.mtrlsBox.currentText() mtrlIdx = self.stratum.cstmIndx[mtrl] self.rIdxRealBox.setValue(mtrlIdx.real) self.rIdxImagBox.setValue(mtrlIdx.imag) if (mtrl in self.stratum.cstmPrd and self.stratum.cstmPrd[mtrl][0] > 0): self.periodBox.setValue(self.stratum.cstmPrd[mtrl][0]) self.repeatBox.setValue(self.stratum.cstmPrd[mtrl][1]) else: self.periodBox.setValue(0.0) self.repeatBox.setValue(0) self.repeatBox.setEnabled(False) if mtrl in self.stratum.cstmGain: self.gainCoeffBox.setValue(self.stratum.cstmGain[mtrl]) else: self.rIdxRealBox.setValue(1.0) self.rIdxRealBox.setEnabled(False) self.rIdxImagBox.setValue(0.0) self.rIdxImagBox.setEnabled(False) self.periodBox.setValue(0.0) self.periodBox.setEnabled(False) return @pyqtSlot(float) def input_rIdx(self, value): """SLOT connected to self.rIdxRealBox.valueChanged[float]""" mtrl = self.mtrlsBox.currentText() alpha = self.stratum.cstmIndx[mtrl].imag self.stratum.cstmIndx[mtrl] = value + 1j * alpha self.dirty.emit() @pyqtSlot(float) def input_alpha(self, value): """SLOT connected to self.rIdxImagBox.valueChanged[float]""" mtrl = self.mtrlsBox.currentText() neff = self.stratum.cstmIndx[mtrl].real self.stratum.cstmIndx[mtrl] = neff + 1j * value self.dirty.emit() @pyqtSlot(float) def input_period(self, value): """SLOT connected to self.periodBox.valueChanged[float]""" mtrl = self.mtrlsBox.currentText() if mtrl not in self.stratum.cstmPrd: self.stratum.cstmPrd[mtrl] = [value, 1] else: self.stratum.cstmPrd[mtrl][0] = value if value == 0: self.repeatBox.setEnabled(False) else: self.repeatBox.setEnabled(True) self.update_customLength() @pyqtSlot(int) def input_repeats(self, value): """SLOT connected to self.repeatBox.valueChanged[int]""" mtrl = self.mtrlsBox.currentText() if mtrl not in self.stratum.cstmPrd: self.stratum.cstmPrd[mtrl] = [1, value] else: self.stratum.cstmPrd[mtrl][1] = value self.update_customLength() @pyqtSlot(float) def input_gainCoeff(self, value): """SLOT connected to self.gainCoeffBox.valueChanged[float]""" mtrl = self.mtrlsBox.currentText() self.stratum.cstmGain[mtrl] = value def setupActive(self, wl, EField, gaincoeff, rIdx, Lp): """Interface to get parameters from quantum tab""" self.wlBox.setValue(wl) mtrl = 'Active Core' # self.stratum.wl = wl self.stratum.cstmIndx[mtrl] = rIdx if 'Active Core' in self.stratum.cstmPrd: self.stratum.cstmPrd[mtrl][0] = Lp else: self.stratum.cstmPrd[mtrl] = [Lp, 1] self.stratum.cstmGain[mtrl] = gaincoeff # set up active core if self.mtrlsBox.findText(mtrl) == -1: self.mtrlsBox.addItem(mtrl) self.mtrlsBox.setCurrentText(mtrl) self.fieldBox.setValue(EField) self.periodBox.setValue(Lp) self.gainCoeffBox.setValue(gaincoeff) self.update_customLength() self.updateMtrl() def update_customLength(self): length = self.periodBox.value() * self.repeatBox.value() / 1E4 # to um for n, mtrl in enumerate(self.stratum.materials): if mtrl == self.mtrlsBox.currentText(): self.stratum.Ls[n] = length self.dirty.emit() def input_facet(self, n, idx): """SLOT as partial(self.input_facet, n) connected to facetBox[n].currentIndexChanged(int)""" self.facet[n] = facetList[idx] self.refBox[n].setValue(self.facetRefct(n) * 100) if self.facet[n] == "custom": self.refBox[n].setEnabled(True) self.refBox[n].blockSignals(False) else: self.refBox[n].setEnabled(False) self.refBox[n].blockSignals(True) self.update_Loss() self.dirty.emit() def input_ref(self, n, ref): """SLOT as partial(self.input_facet, n) connected to facetBox[n].currentIndexChanged(int)""" self.update_Loss() self.dirty.emit() def facetRefct(self, n): """Return the reflectivity of facet n""" if self.facet[n] == 'cleaved': if self.beta is None: return -1 R = abs((self.beta.real - 1) / (self.beta.real + 1))**2 self.refBox[n].setValue(100 * R) return R if self.facet[n] == 'perfect AR': return 1E-9 if self.facet[n] == 'perfect HR': return 1 if self.facet[n] == 'custom': return self.refBox[n].value() / 100 else: raise ValueError("Wrong facet %s" % self.facet[n]) @pyqtSlot(float) def input_ridgeL(self, value): """SLOT connected to ridgeLengthBox.valueChanged[float]""" self.ridgeLength = value self.update_Loss() self.dirty.emit() def update_Loss(self): """Update the mirror loss, should be called whenever the facet settings are changed""" perRunLoss = self.facetRefct(1) * self.facetRefct(0) self.alphaM = -log(perRunLoss) / (2 * self.ridgeLength / 10) # to cm-1 self.mirrorLoss.setText("<center>%.1f cm<sup>-1</sup></center>" % self.alphaM) @pyqtSlot() def strataTable_select(self): """SLOT connected to SIGNAL strataTable.itemSelectionChanged""" row = self.strataTable.currentRow() if row < len(self.stratum.materials): self.select = row else: self.select = None self.stratTable.clearSelection() self.update_canvas() @pyqtSlot(QTableWidgetItem) def strataTable_item(self, item): """SLOT connected to strataTable.itemChanged""" row = item.row() column = item.column() if column in (1, 2, 3): try: value = float(item.text()) if value < 0: raise ValueError except ValueError: # invalid input QMessageBox.warning( self, ejError, "This value should be " "a non-negative number") self.strataTable_refresh() return if column == 1: # mole fraction if value > 1: QMessageBox.warning( self, ejError, "Mole Fraction must be between 0 and 1") self.strataTable.setItem( row, column, QTableWidgetItem("%.2f" % self.stratum.moleFracs[row])) return self.stratum.moleFracs[row] = value if column == 2: # width self.stratum.Ls[row] = value if column == 3: # doping self.stratum.dopings[row] = value self.stratum.updateIndices() self.dirty.emit() @pyqtSlot() def insert_strata(self): """SLOT connected to self.insertButton.clicked()""" row = self.strataTable.currentRow() if row >= len(self.stratum.materials) or row < 0: # Add new lines in the last strata row = len(self.stratum.materials) - 1 elif row == 0: row = 1 # len(self.stratum.materials) is at least 2 for top and substrate # s.t. row >= 1 and <= len(self.stratum.materials) - 1 self.stratum.insert(row) self.dirty.emit() self.strataTable.selectRow(row) @pyqtSlot() def delete_strata(self): """SLOT connected to self.deleteButton.clicked()""" row = self.strataTable.currentRow() if row >= len(self.stratum.materials) - 1 or row < 1: return # s.t. len(self.stratum.materials) > 2 and Ls is not empty self.stratum.delete(row) self.dirty.emit() self.strataTable.selectRow(row) def strataTable_refresh(self): """Update strataTable content, should be called whenever the strata structure is changed""" self.strataTable.blockSignals(True) self.stratum.updateIndices() self.strataTable.clear() self.strataTable.setColumnCount(5) self.strataTable.setHorizontalHeaderLabels( ["Material", "x", "Width", "Doping", "n"]) self.strataTable.setRowCount(len(self.stratum.materials)) self.strataTable.setVerticalHeaderLabels( str(n + 1) for n in range(len(self.stratum.materials))) for q, mtrl in enumerate(self.stratum.materials): # material name mtrlName = mtrlComboBox() mtrlName.addItems(mtrlList) mtrlName.addItems(self.stratum.cstmIndx.keys()) mtrlName.setCurrentText(mtrl) mtrlName.currentTextChanged.connect( partial(self.strataTable_mtrlChanged, q)) self.strataTable.setCellWidget(q, 0, mtrlName) # Mole Frac if mtrl not in Alloy: moleFrac = QTableWidgetItem('N/A') moleFrac.setFlags(Qt.ItemIsSelectable) else: moleFrac = QTableWidgetItem("%.2f" % self.stratum.moleFracs[q]) # moleFrac.setTextAlignment(Qt.AlignCenter) self.strataTable.setItem(q, 1, moleFrac) # Thickness if q == 0 or q == len(self.stratum.materials) - 1: # TODO: To check the best default width for substrate and air thickness = QTableWidgetItem('1.0' if q == 0 else '3.0') thickness.setFlags(Qt.ItemIsSelectable) else: thickness = QTableWidgetItem("%.2f" % self.stratum.Ls[q]) if mtrl in self.stratum.cstmIndx: thickness.setFlags(Qt.ItemIsSelectable) self.strataTable.setItem(q, 2, thickness) # doping if mtrl in Dopable: doping = QTableWidgetItem("%.1f" % self.stratum.dopings[q]) else: doping = QTableWidgetItem("N/A") doping.setFlags(Qt.ItemIsSelectable) self.strataTable.setItem(q, 3, doping) # refractive index if q == 0: ridx = self.stratum.index0 elif q == len(self.stratum.materials) - 1: ridx = self.stratum.indexS else: ridx = self.stratum.indices[q - 1] ridx = QTableWidgetItem("%.3f + %.3fi" % (ridx.real, ridx.imag)) ridx.setFlags(Qt.ItemIsSelectable) self.strataTable.setItem(q, 4, ridx) self.strataTable.resizeColumnsToContents() self.strataTable.blockSignals(False) def strataTable_mtrlChanged(self, row, selection): """SLOT as partial(self.strataTable_mtrlChanged, q) connected to mtrlName.currentTextChanged(str)""" self.stratum.materials[row] = selection if selection == 'Active Core': self.update_customLength() self.strataTable.selectRow(row) self.dirty.emit() @pyqtSlot() def update_xs(self): """Update position vector for plotting and confinement factor integral, also as SLOT to self.dirty SGINAL""" self.beta = None self.optimizeButton.setEnabled(False) # self.stratum.updateIndices() self.strataTable_refresh() self.xs = np.linspace(-1, sum(self.stratum.Ls[1:]), 5000) self.update_canvas() @pyqtSlot() def solve(self): """SLOT connected to self.solveButton.clicked Yield ----- Ey : np.ndarray(complex) The field to plot, normalized to max(abs(Ey)) = 1 confinement : float The confinement factor in unit 1 (percentage 100*confinement) alphaW : float The waveguide loss in unit cm-1 """ try: self.beta = self.stratum.boundModeTM() except (TimeoutError, ValueError): QMessageBox.warning(self, ejError, "Failed to solve for modes") return self.Ey, _, _ = self.stratum.populateMode(self.beta, self.xs) # TODO self.confinement = self.stratum.confinementy(self.beta, self.xs, self.Ey) self.alphaW = 4 * pi / (self.stratum.wl / 1E4) * self.beta.imag # cm^-1 self.update_canvas() self.update_Loss() self.update_info() self.optimizeButton.setEnabled(True) return self.Ey def update_info(self): """Update information in the info box""" info = "" if self.beta is not None: info += "Effective refractive index:\n" info += " β = %.3f + (%.3g)i\n" % (self.beta.real, self.beta.imag) info += "Waveguide loss:\n" info += " α<sub>w</sub> = %.3f cm<sup>-1</sup>\n" % self.alphaW info += "Confinement factor: " info += " Γ = %.1f%%\n" % (self.confinement * 100) info += "Threshold gain:\n" gth = (self.alphaM + self.alphaW) / self.confinement info += " g<sub>th</sub> = %.1f cm<sup>-1</sup>\n" % gth try: self.jth = gth / self.stratum.cstmGain["Active Core"] info += "Threshold current:\n" info += " J<sub>th</sub> = %.1f kA/cm<sup>-2</sup>" % self.jth except (AttributeError, ZeroDivisionError, KeyError): info += "\nUse the quantum tab to define Active region" self.infoBox.setHtml(info.replace("\n", "<br>")) def update_canvas(self): """Update figure in optCanvas""" self.rIdxAxes.clear() self.modeAxes.clear() nx = self.stratum.populateIndices(self.xs) self.rIdxAxes.plot(self.xs, nx.real, 'k', lw=1) self.rIdxAxes.set_xlabel('Position (μm)') self.rIdxAxes.set_ylabel('Mode Intensity (a.u.) or Refractive Index') if self.redActive: for ar in self.stratum.populateMtrl(self.xs): self.rIdxAxes.plot(self.xs[ar], nx.real[ar], 'r', lw=2) if np.max(nx.real) > 5: self.rIdxAxes.set_ylim(top=5) if self.plotImag: self.rIdxAxes.plot(self.xs, nx.imag, 'orange', lw=1) if np.max(nx.imag) > 5: self.rIdxAxes.set_ylim(top=5) # plot select strata if self.select is not None: if self.select == 0: idx = self.xs < 0 elif self.select == len(self.stratum.Ls) - 1: idx = self.xs > sum(self.stratum.Ls[1:-1]) else: lsum = sum(self.stratum.Ls[1:self.select]) idx = (self.xs >= lsum) & (self.xs < lsum + self.stratum.Ls[self.select]) self.rIdxAxes.plot(self.xs[idx], nx.real[idx], 'b', lw=1.5) if self.beta is not None: self.modeAxes.plot(self.xs, np.abs(self.Ey)**2, color='C0') # self.optCanvas.figure.tight_layout() self.optCanvas.draw() @pyqtSlot() def view_redActive(self): self.redActive = not self.redActive self.update_canvas() @pyqtSlot() def optimizeStrata(self): # TODO use threadRun in QuantumTab # TODO: block the button before solve nmax = len(self.stratum.Ls) - 2 toOptimize, lmax, buttonRes = OptimizeInfoDialog( self, [ self.stratum.materials[q + 1] not in self.stratum.cstmIndx for q in range(nmax) ], nmax, sum(self.stratum.Ls[1:-1])).exec() if buttonRes: optimizeOptStrata(self.stratum, self.alphaM, toOptimize, lmax) self.strataTable_refresh() self.solve()
class AdvSetTab(QWidget): def __init__(self, gui): super(AdvSetTab, self).__init__() self._ui = gui self._config = self._ui.config self._tran = self._ui.tran self._parser = self._ui.configparser self.setObjectName("advtab") # main layout of this tab self._layout = QGridLayout(self) self._layout.setObjectName('advtab_layout') # setup the new button self._button_new = QPushButton(self) self._button_new.setObjectName('advtab_button_new') self._button_new.setText(self._tran.get_text(self._button_new.objectName())) self._button_new.clicked.connect(self._new_setting) # setup the delete button self._button_del = QPushButton(self) self._button_del.setObjectName('advtab_button_delete') self._button_del.setText(self._tran.get_text(self._button_del.objectName())) self._button_del.clicked.connect(self._delete_setting) # setup the delete all self._button_del_all = QPushButton(self) self._button_del_all.setObjectName('advtab_button_delete_all') self._button_del_all.setText(self._tran.get_text(self._button_del_all.objectName())) self._button_del_all.clicked.connect(self._delete_config) # setup the reset all button self._button_res_all = QPushButton(self) self._button_res_all.setObjectName('advtab_button_reset_all') self._button_res_all.setText(self._tran.get_text(self._button_res_all.objectName())) self._button_res_all.clicked.connect(self._reset_config) # setup the option table self._table = QTableWidget(0, 2) self._table.setObjectName('advtab_table') self._table.setHorizontalHeaderLabels([self._tran.get_text('option'), self._tran.get_text('value')]) self._table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self._table.setSelectionBehavior(QAbstractItemView.SelectRows) self._table.setSelectionMode(QAbstractItemView.SingleSelection) self._table.setShowGrid(False) self._table.setGeometry(0, 0, 800, 400) self._table.itemChanged.connect(self._changed_setting) # add the elements to the top level layout of the tab self._layout.addWidget(self._button_new, 0, 0, 1, 1) self._layout.addWidget(self._button_del, 0, 1, 1, 1) self._layout.addWidget(self._button_del_all, 0, 2, 1, 1) self._layout.addWidget(self._button_res_all, 0, 3, 1, 1) self._layout.addWidget(self._table, 1, 0, 4, 4) # set the array with all elements that have tooltips self._tooltips = [ self._button_new, self._button_del, self._button_del_all, self._button_res_all, ] def _changed_setting(self): """ Update the config with the values from the ui :return: """ # run through the table for a in range(self._table.rowCount()): # get key and value of the current row _key = self._table.item(a, 0).text() if self._table.item(a, 0) else "" _value = self._table.item(a, 1).text() if self._table.item(a, 1) else "" # update the config self._config[_key] = _value # make the default config visible in the ui, toggle tooltips, ... self.init() def _new_setting(self): """ Add a new setting to the config :return: None """ # get the number of rows in the table _rows = self._table.rowCount() # create a wizard to get the setting name and setting value from the user _wizard = OptionWizard(self._tran.get_text('option_wizard_title'), self._tran) # open the wizard _wizard.exec_() # only if the user has entered a setting name if _wizard.setting_name(): # create a new item holding the entered data self._table.insertRow(_rows) _key = QTableWidgetItem(_wizard.setting_name()) # make the new item editable and selectable _flags = _key.flags() _flags |= Qt.ItemIsSelectable _flags &= Qt.ItemIsEditable _key.setFlags(_flags) # add the new item to the table self._table.setItem(_rows, 0, _key) self._table.setItem(_rows, 1, QTableWidgetItem(_wizard.setting_value())) # add the new setting to the config self._config[_wizard.setting_name()] = _wizard.setting_value() # make the default config visible in the ui, toggle tooltips, ... self.init() def _delete_setting(self): """ Remove the selected setting from config :return: None """ # get the index of the selected row try: _row = self._table.selectedItems()[0].row() except KeyError: return # delete the options from config del self._config[self._table.selectedItems()[0].text()] # remove the row from the table self._table.removeRow(_row) # make the default config visible in the ui, toggle tooltips, ... self.init() def _delete_config(self): """ Remove all setting from config :return: None """ # clear the config self.config = {} # make the default config visible in the ui, toggle tooltips, ... self.init() def _reset_config(self): """ Reset the config to default state :return: None """ # reset config to default self._config = cc_con.DEFAULT_CONFIG # make the default config visible in the ui, toggle tooltips, ... self.init() def toggle_tooltips(self, onoff): """ If "onoff" is either "True" or "true", then show tooltips for this tab, otherwise dont :param onoff: whether to show verbose tooltips :return: None """ if onoff == "True" or onoff == "true": # run through the list with elements that have tooltips... for ele in self._tooltips: # and activate the tooltip for each element ele.setToolTip(self._tran.get_text(ele.objectName() + '_tooltip')) else: # run through the list with elements that have tooltips... for ele in self._tooltips: # and disable the tooltip for each element ele.setToolTip("") def lock(self): """ Disable the ui components, so that the user cannot change settings while the program is running :return: None """ self._toggle_ui_components(False) def unlock(self): """ Enable the ui components, so that the user can change settings :return: None """ self._toggle_ui_components(True) def _toggle_ui_components(self, onoff): """ Enable/Disable ui components (DO NOT USE THIS DIRECTLY; use _lock()/_unlock()) :param onoff: True to enable, False to disable :return: None """ # enable/disable buttons self._button_del.setEnabled(onoff) self._button_del_all.setEnabled(onoff) self._button_new.setEnabled(onoff) self._button_res_all.setEnabled(onoff) # enable/disable editing of the items in the table for a in range(0, self._table.rowCount()): if onoff: self._table.setEnabled(True) else: self._table.setEnabled(False) def init(self): """ What to do, when this tab is activated: fill the table with the contents of the config, ... :return: None """ # prevent sending signals: otherwise this caused the itemChanged Signal of the table to fire which in turn # invokes this method, ... -> infinitely deep recursion self._table.blockSignals(True) # delete all rows from the table while self._table.rowCount() > 0: self._table.removeRow(0) # fill the table with the contents of the config _i = 0 for key, value in self._config.items(): self._table.insertRow(_i) self._table.setItem(_i, 0, QTableWidgetItem(key)) self._table.setItem(_i, 1, QTableWidgetItem(value)) _i += 1 # toggle tooltips if necessary if cc_con.SHOW_VERB_TOOL in self._config: self.toggle_tooltips(self._config[cc_con.SHOW_VERB_TOOL]) # if the options for showing tooltips has not been set they are shown per default else: self.toggle_tooltips("True") self._table.blockSignals(False) def config(self): """ Return the internal configuration of this tab :return: A dictionary containing the configuration """ return self._config