class FpsDlg(QFilterConfig): def _createControls(self): self.setWindowTitle("Configure Crop") layout = QVBoxLayout(self) self.setLayout(layout) self.sourceWidget = QWidget(self) self.sourceSelection = self.createSourceControl(self.sourceWidget) self.sourceSelection.currentDataChanged.connect(self.setFilterSource) srclayout = QHBoxLayout() srclayout.addWidget(QLabel("Source: ", self.sourceWidget)) srclayout.addWidget(self.sourceSelection) self.sourceWidget.setLayout(srclayout) layout.addWidget(self.sourceWidget) regex = QRegExp(r"^(\d+(?:\.\d+)?|\.\d+|\d+/\d+)$") validator = QRegExpValidator(regex) fpsLabel = QLabel("Frame rate:", self) self.fpsEdit = QLineEdit(self) self.fpsEdit.setText("24000/1001") self.fpsEdit.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.fpsEdit.setValidator(validator) self.fpsEdit.textChanged.connect(self.handleRateChanged) hlayout = QHBoxLayout() hlayout.addWidget(fpsLabel) hlayout.addWidget(self.fpsEdit) layout.addLayout(hlayout) self._prepareDlgButtons() def createNewFilterInstance(self): return Fps() @pyqtSlot(str) def handleRateChanged(self, value): if not regex.match(r"^(\d+(?:\.\d+)?|\.\d+|\d+/\d+)$", value): return if regex.match(r"^\d+/\d+$", value): value = QQ(value) elif regex.match(r"^\d+$", value): value = int(value) else: value = float(value) self.filtercopy.rate = value self.isModified() def _resetControls(self): self.fpsEdit.blockSignals(True) self.fpsEdit.setText(f"{self.filtercopy.rate}") self.fpsEdit.blockSignals(False)
def valueChangedCallbackPreTreat(self, val): if len(val) > self.nelem: val = str(val)[:self.nelem] self.updateView(val) QLineEdit.blockSignals(self, True) self.WidgetValueChangedCallback(val) QLineEdit.blockSignals(self, False)
class CustLabel(QWidget): def __init__(self, parent=None): super(CustLabel, self).__init__(parent) # Create ui self.myEdit = QLineEdit() self.myEdit.hide() # Hide line edit self.myEdit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.myEdit.editingFinished.connect(self.textEdited) self.myLabel = BuddyLabel( self.myEdit ) # Create our custom label, and assign myEdit as its buddy # self.myLabel.setText("Nothing has been entered") self.myEdit.setText(self.myLabel.text()) self.myLabel.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed ) # Change vertical size policy so they both match and you don't get popping when switching # Put them under a layout together hLayout = QHBoxLayout() hLayout.addWidget(self.myLabel) hLayout.addWidget(self.myEdit) self.setFocus( ) # By default this line edit may have focus and the place holder won't show up on load, so focus on the widget self.setLayout(hLayout) self.callback = self.empty_callback def empty_callback(self, *args): print("empty callback") pass def textEdited(self): print("edit text") self.myLabel.setText(self.myEdit.text()) # If the input is left empty, revert back to the label showing if not self.myEdit.text(): self.myLabel.setText("Nothing has been entered") # hide() triggers editingFinished causing double callback # blocking signals stops this self.myEdit.blockSignals(True) self.myEdit.hide() self.myEdit.blockSignals(False) self.myLabel.show() self.callback(self) def mousePressEvent(self, event): self.myEdit.hide() self.myLabel.setText(self.myEdit.text()) if not self.myEdit.text() or not self.myLabel.text(): self.myLabel.setText("Nothing has been entered") self.myLabel.show() def putText(self, label_text): self.myLabel.setText(label_text) self.myEdit.setText(label_text)
class Header(QHeaderView): def __init__(self, orientation=Qt.Horizontal, parent=None): super(Header, self).__init__(orientation, parent) self.editable = True self.setSectionsClickable(True) self.setSectionResizeMode(QHeaderView.ResizeToContents) self.line = QLineEdit(parent=self.viewport()) self.line.setAlignment(Qt.AlignTop) self.line.setHidden(True) self.line.blockSignals(True) self.col = 0 # Connections self.sectionDoubleClicked[int].connect(self.__edit) self.line.editingFinished.connect(self.__done_editing) def __edit(self, index): if not self.editable: return geo = self.line.geometry() geo.setWidth(self.sectionSize(index)) geo.moveLeft(self.sectionViewportPosition(index)) current_text = self.model().headerData(index, Qt.Horizontal, Qt.DisplayRole) self.line.setGeometry(geo) self.line.setHidden(False) self.line.blockSignals(False) self.line.setText(str(current_text)) self.line.setFocus() self.line.selectAll() self.col = index def __done_editing(self): text = self.line.text() if not text.strip(): # No debe ser vacío QMessageBox.critical(self, "Error", self.tr("El campo no debe ser vacío")) self.line.hide() return self.line.blockSignals(True) self.line.setHidden(False) self.model().setHeaderData(self.col, Qt.Horizontal, text, Qt.DisplayRole) self.line.setText("") self.line.hide() self.setCurrentIndex(QModelIndex())
class MetaHeaderView(QHeaderView): # source code, and do some miner changes # https://www.qtcentre.org/threads/12835-How-to-edit-Horizontal-Header-Item-in-QTableWidget?p=224376#post224376 def __init__(self, orientation, parent=None): super(MetaHeaderView, self).__init__(orientation, parent) # self.setMovable(True) # self.setClickable(True) self.setSectionsClickable(True) # This block sets up the edit line by making setting the parent # to the Headers Viewport. self.line = QLineEdit(parent=self.viewport()) # Create self.line.setAlignment(Qt.AlignTop) # Set the Alignmnet self.line.setHidden(True) # Hide it till its needed # This is needed because I am having a werid issue that I believe has # to do with it losing focus after editing is done. self.line.blockSignals(True) self.sectionedit = 0 # Connects to double click self.sectionDoubleClicked.connect(self.editHeader) self.line.editingFinished.connect(self.doneEditing) def doneEditing(self): # This block signals needs to happen first otherwise I have lose focus # problems again when there are no rows self.line.blockSignals(True) self.line.setHidden(True) newname = self.line.text() self.model().setHeaderData(self.sectionedit, Qt.Horizontal, newname) self.setCurrentIndex(QModelIndex()) def editHeader(self, section): # This block sets up the geometry for the line edit edit_geometry = self.line.geometry() edit_geometry.setWidth(self.sectionSize(section)) edit_geometry.moveLeft(self.sectionViewportPosition(section)) self.line.setGeometry(edit_geometry) self.line.setText(str(self.model().headerData(section, Qt.Horizontal))) self.line.setHidden(False) # Make it visiable self.line.blockSignals(False) # Let it send signals self.line.setFocus() self.line.selectAll() self.sectionedit = section
class Header(QHeaderView): def __init__(self, orientation=Qt.Horizontal, parent=None): super(Header, self).__init__(orientation, parent) self.setSectionsClickable(True) self.setSectionResizeMode(QHeaderView.ResizeToContents) self.line = QLineEdit(parent=self.viewport()) self.line.setAlignment(Qt.AlignTop) self.line.setHidden(True) self.line.blockSignals(True) self.col = 0 # Connections self.sectionDoubleClicked[int].connect(self.__edit) self.line.editingFinished.connect(self.__done_editing) def __edit(self, index): geo = self.line.geometry() geo.setWidth(self.sectionSize(index)) geo.moveLeft(self.sectionViewportPosition(index)) current_text = self.model().headerData(index, Qt.Horizontal) self.line.setGeometry(geo) self.line.setHidden(False) self.line.blockSignals(False) self.line.setText(str(current_text)) self.line.setFocus() self.line.selectAll() self.col = index def __done_editing(self): self.line.blockSignals(True) self.line.setHidden(False) text = self.line.text() self.model().setHeaderData(self.col, Qt.Horizontal, text) self.line.setText("") self.line.hide() self.setCurrentIndex(QModelIndex())
class OptionsDialog(QDialog): def __init__(self, setting: Settings, have_dutils, parent=None): super(OptionsDialog, self).__init__(parent) self.settings = setting self.enabled_video = True # temporary toggle to disable video features as they do not exist self.enabled_logging = True self.enabled_keybindings = True self.enabled_dutils = have_dutils self.setWindowTitle("Tcam-Capture Options") self.layout = QVBoxLayout(self) self.setLayout(self.layout) self.tabs = QTabWidget() self.general_widget = QWidget() self.keybindings_widget = QWidget() self.logging_widget = QWidget() self.saving_widget = QWidget() self._setup_general_ui() self.tabs.addTab(self.general_widget, "General") self._setup_saving_ui() self.tabs.addTab(self.saving_widget, "Image/Video") self.layout.addWidget(self.tabs) # OK and Cancel buttons self.buttons = QDialogButtonBox( QDialogButtonBox.Reset | QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.layout.addWidget(self.buttons) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.buttons.clicked.connect(self.clicked) def _setup_general_ui(self): """ Create everything related to the general tab """ layout = QFormLayout() layout.setSpacing(20) layout.setVerticalSpacing(20) self.device_dialog_checkbox = QCheckBox(self) device_dialog_label = QLabel("Open device dialog on start:") layout.addRow(device_dialog_label, self.device_dialog_checkbox) self.reopen_device_checkbox = QCheckBox(self) reopen_device_label = QLabel( "Reopen device on start(ignores device dialog):", self) layout.addRow(reopen_device_label, self.reopen_device_checkbox) self.use_dutils_checkbox = QCheckBox(self) self.use_dutils_label = QLabel("Use tiscamera dutils, if present:", self) layout.addRow(self.use_dutils_label, self.use_dutils_checkbox) if not self.enabled_dutils: self.use_dutils_label.setToolTip( "Enabled when tiscamera-dutils are installed") self.use_dutils_label.setEnabled(False) self.use_dutils_checkbox.setToolTip( "Enabled when tiscamera-dutils are installed") self.use_dutils_checkbox.setEnabled(False) self.general_widget.setLayout(layout) def _setup_saving_ui(self): """ Create everything related to the image/video saving tab """ encoder_dict = Encoder.get_encoder_dict() form_layout = QFormLayout() layout = QVBoxLayout() layout.addLayout(form_layout) location_layout = QHBoxLayout() location_label = QLabel("Where to save images/videos:", self) self.location_edit = QLineEdit(self) location_dialog_button = QPushButton("...", self) location_dialog_button.clicked.connect(self.open_file_dialog) location_layout.addWidget(self.location_edit) location_layout.addWidget(location_dialog_button) # maintain descriptions as own labels # pyqt seems to loose the descriptions somewhere # when simple strings are used or the qlabel does not have self as owner form_layout.addRow(location_label, location_layout) self.image_type_combobox = QComboBox(self) for key, value in encoder_dict.items(): if value.encoder_type == Encoder.MediaType.image: self.image_type_combobox.addItem(key) image_type_label = QLabel("Save images as:") self.image_type_combobox.currentIndexChanged['QString'].connect( self.image_name_suffix_changed) form_layout.addRow(image_type_label, self.image_type_combobox) if self.enabled_video: self.video_type_combobox = QComboBox(self) for key, value in encoder_dict.items(): if value.encoder_type == Encoder.MediaType.video: self.video_type_combobox.addItem(key) self.video_type_combobox.currentIndexChanged['QString'].connect( self.video_name_suffix_changed) video_type_label = QLabel("Save videos as:", self) form_layout.addRow(video_type_label, self.video_type_combobox) image_name_groupbox = QGroupBox("Image File Names") groupbox_layout = QFormLayout() image_name_groupbox.setLayout(groupbox_layout) self.image_name_preview = QLabel( "<USER-PREFIX>-<SERIAL>-<FORMAT>-<TIMESTAMP>-<COUNTER>.png") self.image_name_preview_description = QLabel( "Images will be named like:") groupbox_layout.addRow(self.image_name_preview_description, self.image_name_preview) self.image_name_prefix = QLineEdit() self.image_name_prefix.textChanged.connect( self.image_name_prefix_changed) self.image_name_prefix.setMaxLength(100) self.image_name_prefix_description = QLabel("User Prefix:", self) groupbox_layout.addRow(self.image_name_prefix_description, self.image_name_prefix) self.image_name_serial = QCheckBox(self) self.image_name_serial.toggled.connect( self.image_name_properties_toggled) self.image_name_serial_description = QLabel("Include Serial:") groupbox_layout.addRow(self.image_name_serial_description, self.image_name_serial) self.image_name_format = QCheckBox(self) self.image_name_format.toggled.connect( self.image_name_properties_toggled) self.image_name_format_description = QLabel("Include Format:") groupbox_layout.addRow(self.image_name_format_description, self.image_name_format) self.image_name_counter = QCheckBox(self) self.image_name_counter.toggled.connect( self.image_name_properties_toggled) self.image_name_counter_description = QLabel("Include Counter:") groupbox_layout.addRow(self.image_name_counter_description, self.image_name_counter) self.image_name_counter_box = QSpinBox(self) self.image_name_counter_box.setRange(1, 10) self.image_name_counter_box.valueChanged.connect( self.image_name_counter_changed) self.image_name_counter_box_description = QLabel("Counter Size:") groupbox_layout.addRow(self.image_name_counter_box_description, self.image_name_counter_box) self.image_name_counter.toggled.connect( self.toggle_image_counter_box_availability) self.image_name_counter.toggled.connect( self.image_name_properties_toggled) self.image_name_timestamp = QCheckBox(self) self.image_name_timestamp.toggled.connect( self.image_name_properties_toggled) self.image_name_timestamp_description = QLabel("Include Timestamp:") groupbox_layout.addRow(self.image_name_timestamp_description, self.image_name_timestamp) layout.addWidget(image_name_groupbox) video_groupbox = QGroupBox("Video File Names") video_layout = QFormLayout() video_groupbox.setLayout(video_layout) self.video_name_preview = QLabel( "<USER-PREFIX>-<SERIAL>-<FORMAT>-<TIMESTAMP>-<COUNTER>.png") self.video_name_preview_description = QLabel( "Videos will be named like:") video_layout.addRow(self.video_name_preview_description, self.video_name_preview) self.video_name_prefix = QLineEdit() self.video_name_prefix.textChanged.connect( self.video_name_prefix_changed) self.video_name_prefix.setMaxLength(100) self.video_name_prefix_description = QLabel("User Prefix:", self) video_layout.addRow(self.video_name_prefix_description, self.video_name_prefix) self.video_name_serial = QCheckBox(self) self.video_name_serial.toggled.connect( self.video_name_properties_toggled) self.video_name_serial_description = QLabel("Include Serial:") video_layout.addRow(self.video_name_serial_description, self.video_name_serial) self.video_name_format = QCheckBox(self) self.video_name_format.toggled.connect( self.video_name_properties_toggled) self.video_name_format_description = QLabel("Include Format:") video_layout.addRow(self.video_name_format_description, self.video_name_format) self.video_name_counter = QCheckBox(self) self.video_name_counter.toggled.connect( self.video_name_properties_toggled) self.video_name_counter_description = QLabel("Include Counter:") video_layout.addRow(self.video_name_counter_description, self.video_name_counter) self.video_name_counter_box = QSpinBox(self) self.video_name_counter_box.setRange(1, 10) self.video_name_counter_box.valueChanged.connect( self.video_name_counter_changed) self.video_name_counter_box_description = QLabel("Counter Size:") video_layout.addRow(self.video_name_counter_box_description, self.video_name_counter_box) self.video_name_counter.toggled.connect( self.toggle_video_counter_box_availability) self.video_name_counter.toggled.connect( self.video_name_properties_toggled) self.video_name_timestamp = QCheckBox(self) self.video_name_timestamp.toggled.connect( self.video_name_properties_toggled) self.video_name_timestamp_description = QLabel("Include Timestamp:") video_layout.addRow(self.video_name_timestamp_description, self.video_name_timestamp) layout.addWidget(video_groupbox) self.saving_widget.setLayout(layout) def image_name_prefix_changed(self, name: str): """""" self.settings.image_name.user_prefix = self.image_name_prefix.text() self.update_image_name_preview() def image_name_suffix_changed(self, suffix: str): """""" self.update_image_name_preview() def image_name_counter_changed(self, name: str): """""" self.settings.image_name.counter_size = self.image_name_counter_box.value( ) self.update_image_name_preview() def image_name_properties_toggled(self): """""" self.settings.image_name.include_timestamp = self.image_name_timestamp.isChecked( ) self.settings.image_name.include_counter = self.image_name_counter.isChecked( ) self.settings.image_name.include_format = self.image_name_format.isChecked( ) self.settings.image_name.include_serial = self.image_name_serial.isChecked( ) self.update_image_name_preview() def update_image_name_preview(self): preview_string = "" if self.settings.image_name.user_prefix != "": max_prefix_length = 15 prefix = ( self.settings.image_name.user_prefix[:max_prefix_length] + '..') if len( self.settings.image_name.user_prefix ) > max_prefix_length else self.settings.image_name.user_prefix preview_string += prefix if self.settings.image_name.include_serial: if preview_string != "": preview_string += "-" preview_string += "00001234" if self.settings.image_name.include_format: if preview_string != "": preview_string += "-" preview_string += "gbrg_1920x1080_15_1" if self.settings.image_name.include_timestamp: if preview_string != "": preview_string += "-" preview_string += "19701230T125503" if self.settings.image_name.include_counter: if preview_string != "": preview_string += "-" preview_string += '{message:0>{fill}}'.format( message=1, fill=self.settings.image_name.counter_size) if preview_string == "": preview_string = "image" preview_string += "." + self.image_type_combobox.currentText() self.image_name_preview.setText(preview_string) def video_name_prefix_changed(self, name: str): """""" self.settings.video_name.user_prefix = self.video_name_prefix.text() self.update_video_name_preview() def video_name_suffix_changed(self, suffix: str): """""" self.update_video_name_preview() def video_name_counter_changed(self, name: str): """""" self.settings.video_name.counter_size = self.video_name_counter_box.value( ) self.update_video_name_preview() def video_name_properties_toggled(self): """""" self.settings.video_name.include_timestamp = self.video_name_timestamp.isChecked( ) self.settings.video_name.include_counter = self.video_name_counter.isChecked( ) self.settings.video_name.include_format = self.video_name_format.isChecked( ) self.settings.video_name.include_serial = self.video_name_serial.isChecked( ) self.update_video_name_preview() def update_video_name_preview(self): preview_string = "" if self.settings.video_name.user_prefix != "": # This is a convenience change to the displayed string. # We only display an amount of max_prefix_length # chars to save screen space max_prefix_length = 15 prefix = ( self.settings.video_name.user_prefix[:max_prefix_length] + '..') if len( self.settings.video_name.user_prefix ) > max_prefix_length else self.settings.video_name.user_prefix preview_string += prefix if self.settings.video_name.include_serial: if preview_string != "": preview_string += "-" preview_string += "00001234" if self.settings.video_name.include_format: if preview_string != "": preview_string += "-" preview_string += "gbrg_1920x1080_15_1" if self.settings.video_name.include_timestamp: if preview_string != "": preview_string += "-" preview_string += "19701230T125503" if self.settings.video_name.include_counter: if preview_string != "": preview_string += "-" preview_string += '{message:0>{fill}}'.format( message=1, fill=self.settings.video_name.counter_size) if preview_string == "": preview_string = "video" preview_string += "." + self.video_type_combobox.currentText() self.video_name_preview.setText(preview_string) def toggle_image_counter_box_availability(self): """""" if self.image_name_counter.isChecked(): self.image_name_counter_box.setEnabled(True) else: self.image_name_counter_box.setEnabled(False) def toggle_video_counter_box_availability(self): """""" if self.video_name_counter.isChecked(): self.video_name_counter_box.setEnabled(True) else: self.video_name_counter_box.setEnabled(False) def set_settings(self, settings: Settings): self.location_edit.setText(settings.get_save_location()) self.image_type_combobox.setCurrentText(settings.get_image_type()) if self.enabled_video: self.video_type_combobox.setCurrentText(settings.get_video_type()) self.device_dialog_checkbox.setChecked( settings.show_device_dialog_on_startup) self.reopen_device_checkbox.setChecked( settings.reopen_device_on_startup) self.use_dutils_checkbox.setChecked(settings.use_dutils) if settings.image_name.include_timestamp: self.image_name_timestamp.blockSignals(True) self.image_name_timestamp.toggle() self.image_name_timestamp.blockSignals(False) if settings.image_name.include_counter: self.image_name_counter.blockSignals(True) self.image_name_counter.toggle() self.image_name_counter.blockSignals(False) self.image_name_counter_box.blockSignals(True) self.image_name_counter_box.setValue(settings.image_name.counter_size) self.image_name_counter_box.blockSignals(False) self.toggle_image_counter_box_availability() if settings.image_name.include_format: self.image_name_format.blockSignals(True) self.image_name_format.toggle() self.image_name_format.blockSignals(False) if settings.image_name.include_serial: self.image_name_serial.blockSignals(True) self.image_name_serial.toggle() self.image_name_serial.blockSignals(False) self.image_name_prefix.blockSignals(True) self.image_name_prefix.setText(settings.image_name.user_prefix) self.image_name_prefix.blockSignals(False) self.update_image_name_preview() if settings.video_name.include_timestamp: self.video_name_timestamp.blockSignals(True) self.video_name_timestamp.toggle() self.video_name_timestamp.blockSignals(False) if settings.video_name.include_counter: self.video_name_counter.blockSignals(True) self.video_name_counter.toggle() self.video_name_counter.blockSignals(False) self.video_name_counter_box.blockSignals(True) self.video_name_counter_box.setValue(settings.video_name.counter_size) self.video_name_counter_box.blockSignals(False) self.toggle_video_counter_box_availability() if settings.video_name.include_format: self.video_name_format.blockSignals(True) self.video_name_format.toggle() self.video_name_format.blockSignals(False) if settings.video_name.include_serial: self.video_name_serial.blockSignals(True) self.video_name_serial.toggle() self.video_name_serial.blockSignals(False) self.video_name_prefix.blockSignals(True) self.video_name_prefix.setText(settings.video_name.user_prefix) self.video_name_prefix.blockSignals(False) self.update_video_name_preview() def save_settings(self): self.settings.save_location = self.location_edit.text() self.settings.image_type = self.image_type_combobox.currentText() if self.enabled_video: self.settings.video_type = self.video_type_combobox.currentText() self.settings.show_device_dialog_on_startup = self.device_dialog_checkbox.isChecked( ) self.settings.reopen_device_on_startup = self.reopen_device_checkbox.isChecked( ) self.settings.use_dutils = self.use_dutils_checkbox.isChecked() self.settings.image_name.include_timestamp = self.image_name_timestamp.isChecked( ) self.settings.image_name.include_counter = self.image_name_counter.isChecked( ) if self.image_name_counter.isChecked(): self.settings.image_name.counter_size = self.image_name_counter_box.value( ) self.settings.image_name.include_format = self.image_name_format.isChecked( ) self.settings.image_name.include_serial = self.image_name_serial.isChecked( ) self.settings.image_name.user_prefix = self.image_name_prefix.text() self.settings.video_name.include_timestamp = self.video_name_timestamp.isChecked( ) self.settings.video_name.include_counter = self.video_name_counter.isChecked( ) if self.video_name_counter.isChecked(): self.settings.video_name.counter_size = self.video_name_counter_box.value( ) self.settings.video_name.include_format = self.video_name_format.isChecked( ) self.settings.video_name.include_serial = self.video_name_serial.isChecked( ) self.settings.video_name.user_prefix = self.video_name_prefix.text() def open_file_dialog(self): fdia = QFileDialog() fdia.setFileMode(QFileDialog.Directory) fdia.setWindowTitle("Select Directory for saving images and videos") if fdia.exec_(): self.location_edit.setText(fdia.selectedFiles()[0]) def get_location(self): return self.location_edit.text() def get_image_format(self): return self.image_type_combobox.currentText() def get_video_format(self): return self.video_type_combobox.currentText() def clicked(self, button): if self.buttons.buttonRole(button) == QDialogButtonBox.ResetRole: self.reset() def reset(self): """""" log.info("reset called") self.settings.reset() self.set_settings(self.settings) @staticmethod def get_options(settings, parent=None): dialog = OptionsDialog(settings, parent) if settings is not None: dialog.set_settings(settings) result = dialog.exec_() if result == QDialog.Accepted: dialog.save_settings() settings.save() return result == QDialog.Accepted
class energy_to_charge(QWidget): changed = pyqtSignal() def __init__(self): QWidget.__init__(self) self.position = "top" self.charge_type = "electron" self.hbox = QHBoxLayout() self.edit_m3 = QLineEdit() self.edit_m3.setMaximumWidth(60) self.label_m3 = QLabel("m<sup>-3</sup> /") self.label_m3.setStyleSheet("QLabel { border: 0px; padding: 0px; }") self.label_m3.setMaximumWidth(30) self.edit_eV = QLineEdit() self.edit_eV.setMaximumWidth(50) self.label_eV = QLabel("eV") self.label_eV.setStyleSheet("QLabel { border: 0px; padding: 0px; }") self.label_eV.setMaximumWidth(20) #self.button=QPushButton() #self.button.setFixedSize(25, 25) #self.button.setText("...") self.hbox.addWidget(self.edit_m3, Qt.AlignLeft) self.hbox.addWidget(self.label_m3, Qt.AlignLeft) self.hbox.addWidget(self.edit_eV, Qt.AlignLeft) self.hbox.addWidget(self.label_eV, Qt.AlignLeft) self.hbox.setSpacing(0) #self.hbox.addWidget(self.button) self.edit_m3.textChanged.connect(self.callback_m3_changed) self.edit_eV.textChanged.connect(self.callback_eV_changed) self.hbox.setContentsMargins(0, 0, 0, 0) self.edit_m3.setStyleSheet("QLineEdit { border: none }") #self.button.clicked.connect(self.callback_button_click) self.setLayout(self.hbox) def cal_ev(self): for l in get_epi().layers: file_name = l.dos_file + ".inp" if file_name.startswith("dos"): f = inp() f.load(file_name) if self.charge_type == "electron": eV = gen_fermi_from_np(float(self.edit_m3.text()), float(f.get_token("#Nc")), 300.0) else: eV = gen_fermi_from_np(float(self.edit_m3.text()), float(f.get_token("#Nv")), 300.0) return eV def cal_m3(self): for l in get_epi().layers: file_name = l.dos_file + ".inp" if file_name.startswith("dos"): f = inp() f.load(file_name) if self.charge_type == "electron": n = gen_np_from_fermi(float(self.edit_eV.text()), float(f.get_token("#Nc")), 300.0) else: n = gen_np_from_fermi(float(self.edit_eV.text()), float(f.get_token("#Nv")), 300.0) return n def callback_eV_changed(self): n = self.cal_m3() self.edit_m3.blockSignals(True) self.edit_m3.setText('%.0e' % n) self.edit_m3.blockSignals(False) self.changed.emit() def callback_m3_changed(self): try: ev = self.cal_ev() self.edit_eV.blockSignals(True) self.edit_eV.setText('%.2f' % ev) self.edit_eV.blockSignals(False) except: pass self.changed.emit() def setText(self, text): val = float(text) text = '%.2e' % val text = str(decimal.Decimal(text).normalize()).lower().replace('+', '') self.edit_m3.setText(text) #self.edit_eV.setText(text) def text(self): return self.edit_m3.text()
class AnalyizeDataWindow(QWidget): def __init__(self): super().__init__() self.setGeometry(100, 100, 500, 200) self.setWindowTitle("Analyze Datafile") self.layout = QGridLayout() # self.rangeDict = {"Default" : [[0,1],[0,100],[0,100], # [0,100],[0,100],[0,1]]} self.dataAnalDict = {} # self.dataAnalDict['force settings'] = {} self.dataAnalDict['Vertical force'] = {} self.dataAnalDict['Lateral force'] = {} self.initialize_dict("Default", range_clear=True) self.dataAnalDict['misc settings'] = {} self.home() #call this when your are adding a new roi label. #initialize force settings in dict def initialize_dict(self, roi_label, range_clear=True): # self.dataAnalDict['force settings'][roi_label] = {} # self.init_force_dict(roi_label, "Vertical force") # self.init_force_dict(roi_label, "Lateral force") if range_clear == True: self.dataAnalDict["Vertical force"]["ranges"] = {} self.dataAnalDict["Lateral force"]["ranges"] = {} self.init_force_dict("Vertical force", roi_label) self.init_force_dict("Lateral force", roi_label) #initialize sub properties of force def init_force_dict(self, force, roi_label): self.dataAnalDict[force]["ranges"][roi_label] = {} self.dataAnalDict[force]["ranges"][roi_label]["Zero"] = "100,200" self.dataAnalDict[force]["ranges"][roi_label]["Force"] = "100,200" self.dataAnalDict[force]["ranges"][roi_label]["Preload"] = "100,200" self.dataAnalDict[force]["transform"] = {} self.dataAnalDict[force]["transform"]["Filter"] = False self.dataAnalDict[force]["transform"]["Filter window"] = 43 self.dataAnalDict[force]["transform"]["Filter order"] = 2 self.dataAnalDict[force]["transform"]["Cross Talk"] = 0 self.dataAnalDict[force]["transform"]["Zero subtract"] = False # self.dataAnalDict['force settings'][roi_label][force] = {} # self.dataAnalDict['force settings'][roi_label][force]["Zero"] = "0,10" # self.dataAnalDict['force settings'][roi_label][force]["Force"] = "0,10" # self.dataAnalDict['force settings'][roi_label][force]["Preload"] = "0,10" # self.dataAnalDict['force settings'][roi_label][force]["Filter"] = False # self.dataAnalDict['force settings'][roi_label][force]["Filter window"] = 43 # self.dataAnalDict['force settings'][roi_label][force]["Filter order"] = 2 # self.dataAnalDict['force settings'][roi_label][force]["Cross Talk"] = 0 def home(self): # self.showContactArea = QCheckBox('contact area', self) #contact area # self.showContactArea.setChecked(True) # self.showROIArea = QCheckBox('ROI area', self) #roi area # self.showContactLength = QCheckBox('contact length', self) #contact length # self.showROILength = QCheckBox('ROI length', self) #roi length # self.showContactNumber = QCheckBox('contact number', self) #contact number # self.showEcc = QCheckBox('eccentricity', self) #median eccentricity # self.showLateralForce = QCheckBox('lateral force', self) #lateral force # self.showZPiezo = QCheckBox('vertical piezo', self) #z piezo # self.showXPiezo = QCheckBox('lateral piezo', self) #x piezo # self.showAdhesion = QCheckBox('adhesion calculation', self) #adhesion/preload calc line # self.showFriction = QCheckBox('friction calculation', self) #friction calc lines # self.showStress = QCheckBox('stress', self) #stress # self.showDeformation = QCheckBox('deformation', self) #deformation # self.showTitle = QCheckBox('title', self) #plt title # self.showTitle.setChecked(True) # self.showLegend2 = QCheckBox('legend2', self) #plt title # self.showLegend2.setChecked(True) # self.showWidgets = [self.showContactArea, self.showROIArea, self.showZPiezo, # self.showXPiezo, self.showAdhesion, self.showFriction, # self.showLateralForce, self.showContactLength, self.showROILength, # self.showContactNumber, self.showEcc, self.showStress, # self.showDeformation, self.showTitle, self.showLegend2] # self.xAxisLabel = QLabel("<b>X Axis:</b>", self) # self.xAxisParam = QComboBox(self) #x axis parameter # self.xAxisParam.addItem("Time (s)") # self.xAxisParam.addItem("Vertical Position (μm)") # self.xAxisParam.addItem("Lateral Position (μm)") # self.xAxisParam.addItem("Deformation (μm)") # self.fontLabel = QLabel("Font Size:", self) # self.fontSize = QDoubleSpinBox(self) #vertical force zero range start # self.fontSize.setValue(12) # self.fontSize.setSingleStep(1) # self.fontSize.setRange(1, 100) roiChoiceLabel = QLabel("ROI Label:", self) self.roiChoice = QComboBox(self) #choose ROI self.roiChoice.addItem("Default") self.roiChoice.setCurrentIndex(0) self.roiChoice.currentIndexChanged.connect(self.update_widgets) dataChoiceLabel = QLabel("Data:", self) self.dataChoice = QComboBox(self) #force data self.dataChoiceDict = { "Vertical force": "Adhesion", "Lateral force": "Friction" } self.dataChoice.addItems(list(self.dataChoiceDict.keys())) self.dataChoice.setCurrentIndex(0) self.dataChoice.currentIndexChanged.connect(self.update_widgets) self.zeroBtn = QPushButton("Zero Range", self) #zero self.zeroLabel = QLineEdit(self) self.zeroLabel.setReadOnly(True) self.zeroLabel.setText("100,200") self.forceBtn = QPushButton( self.dataChoiceDict[self.dataChoice.currentText()] + " Range", self) #adhesion/friction self.forceLabel = QLineEdit(self) self.forceLabel.setReadOnly(True) self.forceLabel.setText("100,200") self.preloadBtn = QPushButton("Preload Range", self) #preload self.preloadLabel = QLineEdit(self) self.preloadLabel.setReadOnly(True) self.preloadLabel.setText("100,200") # self.startLabel = QLabel("Start (%):", self) # self.endLabel = QLabel("End (%):", self) # self.zeroLabel = QLabel("Zero Range", self) # self.adhLabel = QLabel("Adhesion Range", self) # self.prl1Label = QLabel("Preload Range", self) # self.zeroRange1 = QDoubleSpinBox(self) #vertical force zero range start # self.zeroRange1.setValue(0) # self.zeroRange1.setSingleStep(1) # self.zeroRange1.setRange(0, 100) # self.zeroRange1.valueChanged.connect(self.update_dict) # self.zeroRange2 = QDoubleSpinBox(self) #vertical force zero range end # self.zeroRange2.setValue(1) # self.zeroRange2.setSingleStep(1) # self.zeroRange2.setRange(0, 100) # self.zeroRange2.valueChanged.connect(self.update_dict) # self.adhRange1 = QDoubleSpinBox(self) #adhesion peak range start # self.adhRange1.setValue(0) # self.adhRange1.setSingleStep(1) # self.adhRange1.setRange(0, 100) # self.adhRange1.valueChanged.connect(self.update_dict) # self.adhRange2 = QDoubleSpinBox(self) #adhesion peak range start # self.adhRange2.setValue(100) # self.adhRange2.setSingleStep(1) # self.adhRange2.setRange(0, 100) # self.adhRange2.valueChanged.connect(self.update_dict) # self.prl1Range1 = QDoubleSpinBox(self) #preload peak range start # self.prl1Range1.setValue(0) # self.prl1Range1.setSingleStep(1) # self.prl1Range1.setRange(0, 100) # self.prl1Range1.valueChanged.connect(self.update_dict) # self.prl1Range2 = QDoubleSpinBox(self) #preload peak range start # self.prl1Range2.setValue(100) # self.prl1Range2.setSingleStep(1) # self.prl1Range2.setRange(0, 100) # self.prl1Range2.valueChanged.connect(self.update_dict) # self.zero2Range1 = QDoubleSpinBox(self) #lateral force zero range start # self.zero2Range1.setValue(0) # self.zero2Range1.setSingleStep(1) # self.zero2Range1.setRange(0, 100) # self.zero2Range1.valueChanged.connect(self.update_dict) # self.zero2Range2 = QDoubleSpinBox(self) #lateral force zero range end # self.zero2Range2.setValue(1) # self.zero2Range2.setSingleStep(1) # self.zero2Range2.setRange(0, 100) # self.zero2Range2.valueChanged.connect(self.update_dict) # self.filterLatF = QCheckBox('Filter stress curve', self) #filter self.filter = QCheckBox('Filter', self) #filter # self.filter.stateChanged.connect(self.update_dict) windLabel = QLabel("Window Length:", self) self.filter_wind = QSpinBox(self) #filter window self.filter_wind.setValue(43) self.filter_wind.setSingleStep(20) self.filter_wind.setRange(3, 10001) # self.filter_wind.valueChanged.connect(self.filter_change) polyLabel = QLabel("Polynomial Order:", self) self.filter_poly = QSpinBox(self) #filter polynom self.filter_poly.setValue(2) self.filter_poly.setSingleStep(1) self.filter_poly.setRange(1, 20000) # self.filter_poly.valueChanged.connect(self.update_dict) self.zero_subtract = QCheckBox('Zero subtract', self) #filter # self.startLabel2 = QLabel("Start (%):", self) # self.endLabel2 = QLabel("End (%):", self) # self.frLabel = QLabel("Friction Range", self) # self.prl2Label = QLabel("Preload Range", self) # self.zero2Label = QLabel("Zero Range", self) eqLabel = QLabel("Lateral Calib. Equation (μN):", self) self.latCalibEq = QLineEdit(self) #lateral force calib equation self.latCalibEq.setText("29181.73*x") noiseStepsLabel = QLabel("Noisy Steps:", self) noiseSteps = QLineEdit(self) #remove first data point from steps noiseSteps.setText("") # self.legendPosLabel = QLabel("Legend:", self) #legend position # self.legendPos = QLineEdit(self) # self.legendPos.setText("upper right") # self.startFullLabel = QLabel("Start (%):", self) # self.endFullLabel = QLabel("End (%):", self) # self.startFull = QDoubleSpinBox(self) #plot range start # self.startFull.setValue(0) # self.startFull.setSingleStep(1) # self.startFull.setRange(0, 100) # self.endFull = QDoubleSpinBox(self) #plot range end # self.endFull.setValue(100) # self.endFull.setSingleStep(1) # self.endFull.setRange(0, 100) # self.invertLatForce = QCheckBox('Invert Lateral Force', self) #invert applyCrossTalk = QCheckBox('Apply Cross Talk', self) #cross talk flag # self.zeroShift = QCheckBox('Shift to Zero', self) #force curve shift to zero # self.vertCrossTalk = QDoubleSpinBox(self) #vertical cross talk slope # self.vertCrossTalk.setValue(0) # self.vertCrossTalk.setSingleStep(0.1) # self.vertCrossTalk.setDecimals(4) # self.vertCrossTalk.setRange(-1000, 1000) # self.vertCTlabel = QLabel("Cross Talk (μN/μN):", self) # self.latCrossTalk = QDoubleSpinBox(self) #lateral cross talk slope # self.latCrossTalk.setValue(0) # self.latCrossTalk.setSingleStep(0.1) # self.latCrossTalk.setDecimals(4) # self.latCrossTalk.setRange(-1000, 1000) # self.latCTlabel = QLabel("Cross Talk (μN/μN):", self) CTlabel = QLabel("Cross Talk (μN/μN):", self) # cross talk slope self.crossTalk = QDoubleSpinBox(self) self.crossTalk.setValue(0) self.crossTalk.setSingleStep(0.1) self.crossTalk.setDecimals(4) self.crossTalk.setRange(-1000, 1000) # self.crossTalk.valueChanged.connect(self.update_dict) # self.frictionRange1 = QDoubleSpinBox(self) #friction range start # self.frictionRange1.setValue(0) # self.frictionRange1.setSingleStep(1) # self.frictionRange1.setRange(0, 100) # self.frictionRange1.valueChanged.connect(self.update_dict) # self.frictionRange2 = QDoubleSpinBox(self) #friction range end # self.frictionRange2.setValue(100) # self.frictionRange2.setSingleStep(1) # self.frictionRange2.setRange(0, 100) # self.frictionRange2.valueChanged.connect(self.update_dict) # self.prl2Range1 = QDoubleSpinBox(self) #friction preload peak range start # self.prl2Range1.setValue(0) # self.prl2Range1.setSingleStep(1) # self.prl2Range1.setRange(0, 100) # self.prl2Range1.valueChanged.connect(self.update_dict) # self.prl2Range2 = QDoubleSpinBox(self) #friction preload peak range start # self.prl2Range2.setValue(100) # self.prl2Range2.setSingleStep(1) # self.prl2Range2.setRange(0, 100) # self.prl2Range2.valueChanged.connect(self.update_dict) # self.fitPosLabel = QLabel("Fit Position\n(x,y):", self) #fit eq. position # self.fitPos = QLineEdit(self) # self.fitPos.setText('0.5,0.5') # self.showFitEq = QCheckBox('Show Slope', self) #display equation on plot kBeamLabel = QLabel("Beam Spring Constant (μN/μm):", self) #beam dpring constant kBeam = QLineEdit(self) kBeam.setText('30,1') # deformStartLabel = QLabel("Deformation Start:", self) #contact start tolerance auto detect self.deformBtn = QPushButton("Deformation Range", self) #deformation self.deformLabel = QLineEdit(self) self.deformLabel.setReadOnly(True) # self.deformLabel.textChanged.connect(self.updateRange) self.deformLabel.setText("100,200") # self.deformStart = QSpinBox(self) # self.deformStart.setValue(100) # self.deformStart.setSingleStep(1) # self.deformStart.setRange(0, 10000) # self.dataAnalDict['misc'] = {} self.dataAnalDict['misc settings']['apply cross talk'] = applyCrossTalk self.dataAnalDict['misc settings']['noise steps'] = noiseSteps self.dataAnalDict['misc settings'][ 'deformation range'] = self.deformLabel self.dataAnalDict['misc settings']['beam spring constant'] = kBeam self.okBtn = QPushButton("OK", self) #Close window self.updateBtn = QPushButton("Update", self) #Update #update dictionary on widget value change self.zeroLabel.textChanged.connect(self.update_dict) self.forceLabel.textChanged.connect(self.update_dict) self.preloadLabel.textChanged.connect(self.update_dict) self.filter.stateChanged.connect(self.update_dict) self.filter_wind.valueChanged.connect(self.filter_change) self.filter_poly.valueChanged.connect(self.update_dict) self.zero_subtract.stateChanged.connect(self.update_dict) self.crossTalk.valueChanged.connect(self.update_dict) self.zeroLabel.textChanged.connect(self.update_dict) self.forceLabel.textChanged.connect(self.update_dict) self.preloadLabel.textChanged.connect(self.update_dict) # self.zeroGroupBox = QGroupBox("Configure Vertical Force") # filterGroupBox = QGroupBox("Configure Plot") # flagGroupBox = QGroupBox("Show") # self.latCalibGroupBox = QGroupBox("Configure Lateral Force") # self.fittingGroupBox = QGroupBox("Fit Data") forceGroupBox = QGroupBox("Force") miscGroupBox = QGroupBox("Misc") buttonGroupBox = QGroupBox() forceGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") miscGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # self.zeroGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # filterGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # flagGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # self.latCalibGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # self.fittingGroupBox.setStyleSheet("QGroupBox { font-weight: bold; } ") # self.fittingGroupBox.setCheckable(True) # self.fittingGroupBox.setChecked(False) # self.layout.addWidget(self.roiChoice, 0, 0, 1, 2) # self.layout.addWidget(self.zeroGroupBox, 1, 0) # self.layout.addWidget(filterGroupBox, 2, 1) # self.layout.addWidget(flagGroupBox, 2, 0) # self.layout.addWidget(self.latCalibGroupBox, 1, 1) # self.layout.addWidget(self.fittingGroupBox, 3, 0) self.layout.addWidget(forceGroupBox, 0, 0) self.layout.addWidget(miscGroupBox, 1, 0) self.layout.addWidget(buttonGroupBox, 2, 0) self.setLayout(self.layout) buttonVbox = QGridLayout() buttonGroupBox.setLayout(buttonVbox) buttonVbox.addWidget(self.updateBtn, 0, 0) buttonVbox.addWidget(self.okBtn, 0, 1) forceLayout = QGridLayout() forceGroupBox.setLayout(forceLayout) forceLayout.addWidget(roiChoiceLabel, 0, 0, 1, 1) forceLayout.addWidget(self.roiChoice, 0, 1, 1, 1) forceLayout.addWidget(dataChoiceLabel, 0, 2, 1, 1) forceLayout.addWidget(self.dataChoice, 0, 3, 1, 1) forceLayout.addWidget(self.zeroBtn, 1, 0, 1, 1) forceLayout.addWidget(self.zeroLabel, 1, 1, 1, 1) forceLayout.addWidget(self.forceBtn, 2, 0, 1, 1) forceLayout.addWidget(self.forceLabel, 2, 1, 1, 1) forceLayout.addWidget(self.preloadBtn, 3, 0, 1, 1) forceLayout.addWidget(self.preloadLabel, 3, 1, 1, 1) forceLayout.addWidget(self.filter, 1, 2, 1, 2) forceLayout.addWidget(windLabel, 2, 2, 1, 1) forceLayout.addWidget(self.filter_wind, 2, 3, 1, 1) forceLayout.addWidget(polyLabel, 3, 2, 1, 1) forceLayout.addWidget(self.filter_poly, 3, 3, 1, 1) forceLayout.addWidget(self.zero_subtract, 4, 2, 1, 2) forceLayout.addWidget(CTlabel, 4, 0, 1, 1) forceLayout.addWidget(self.crossTalk, 4, 1, 1, 1) miscLayout = QGridLayout() miscGroupBox.setLayout(miscLayout) miscLayout.addWidget(applyCrossTalk, 0, 0, 1, 2) miscLayout.addWidget(self.deformBtn, 1, 0, 1, 1) miscLayout.addWidget(self.deformLabel, 1, 1, 1, 1) miscLayout.addWidget(noiseStepsLabel, 0, 2, 1, 1) miscLayout.addWidget(noiseSteps, 0, 3, 1, 1) miscLayout.addWidget(kBeamLabel, 1, 2, 1, 1) miscLayout.addWidget(kBeam, 1, 3, 1, 1) miscLayout.addWidget(eqLabel, 2, 0, 1, 1) #remove miscLayout.addWidget(self.latCalibEq, 2, 1, 1, 1) #remove # miscLayout.addWidget(self.zeroShift, 2, 2, 1, 2) #remove # zeroVbox = QGridLayout() # self.zeroGroupBox.setLayout(zeroVbox) # zeroVbox.addWidget(self.zeroLabel, 0, 1, 1, 1) # zeroVbox.addWidget(self.adhLabel, 0, 2, 1, 1) # zeroVbox.addWidget(self.prl1Label, 0, 3, 1, 1) # zeroVbox.addWidget(self.startLabel, 1, 0, 1, 1) # zeroVbox.addWidget(self.endLabel, 2, 0, 1, 1) # zeroVbox.addWidget(self.zeroRange1, 1, 1, 1, 1) # zeroVbox.addWidget(self.zeroRange2, 2, 1, 1, 1) # zeroVbox.addWidget(self.adhRange1, 1, 2, 1, 1) # zeroVbox.addWidget(self.adhRange2, 2, 2, 1, 1) # zeroVbox.addWidget(self.prl1Range1, 1, 3, 1, 1) # zeroVbox.addWidget(self.prl1Range2, 2, 3, 1, 1) # zeroVbox.addWidget(self.vertCTlabel, 3, 0, 1, 1) # zeroVbox.addWidget(self.vertCrossTalk, 3, 1, 1, 1) # filterVbox = QGridLayout() # filterGroupBox.setLayout(filterVbox) # filterVbox.addWidget(self.filterLatF, 1, 0, 1, 2) # filterVbox.addWidget(self.windLabel, 2, 0, 1, 1) # filterVbox.addWidget(self.filter_wind, 2, 1, 1, 1) # filterVbox.addWidget(self.polyLabel, 3, 0, 1, 1) # filterVbox.addWidget(self.filter_poly, 3, 1, 1, 1) # # filterVbox.addWidget(self.fontLabel, 2, 2, 1, 1) # # filterVbox.addWidget(self.fontSize, 2, 3, 1, 1) # filterVbox.addWidget(self.eqLabel, 3, 2, 1, 1) # filterVbox.addWidget(self.latCalibEq, 3, 3, 1, 1) # # filterVbox.addWidget(self.invertLatForce, 0, 2, 1, 2) # filterVbox.addWidget(self.zeroShift, 0, 0, 1, 1) # filterVbox.addWidget(self.applyCrossTalk, 1, 2, 1, 2) # # filterVbox.addWidget(self.xAxisLabel, 0, 3, 1, 1) # # filterVbox.addWidget(self.xAxisParam, 1, 3, 1, 1) # filterVbox.addWidget(self.noiseStepsLabel, 4, 2, 1, 1) # filterVbox.addWidget(self.noiseSteps, 5, 2, 1, 1) # # filterVbox.addWidget(self.legendPosLabel, 4, 3, 1, 1) # # filterVbox.addWidget(self.legendPos, 5, 3, 1, 1) # # filterVbox.addWidget(self.startFullLabel, 4, 0, 1, 1) # # filterVbox.addWidget(self.endFullLabel, 5, 0, 1, 1) # # filterVbox.addWidget(self.startFull, 4, 1, 1, 1) # # filterVbox.addWidget(self.endFull, 5, 1, 1, 1) # filterVbox.addWidget(self.kBeamLabel, 6, 2, 1, 1) # filterVbox.addWidget(self.kBeam, 6, 3, 1, 1) # filterVbox.addWidget(self.deformStartLabel, 6, 0, 1, 1) # filterVbox.addWidget(self.deformStart, 6, 1, 1, 1) # flagVbox = QGridLayout() # flagGroupBox.setLayout(flagVbox) # flagVbox.addWidget(self.showContactArea, 0, 0) # flagVbox.addWidget(self.showROIArea, 0, 1) # flagVbox.addWidget(self.showZPiezo, 0, 2) # flagVbox.addWidget(self.showXPiezo, 1, 0) # flagVbox.addWidget(self.showAdhesion, 1, 1) # flagVbox.addWidget(self.showFriction, 1, 2) # flagVbox.addWidget(self.showLateralForce, 2, 0) # flagVbox.addWidget(self.showContactLength, 2, 1) # flagVbox.addWidget(self.showROILength, 2, 2) # flagVbox.addWidget(self.showContactNumber, 3, 0) # flagVbox.addWidget(self.showEcc, 3, 1) # flagVbox.addWidget(self.showStress, 3, 2) # flagVbox.addWidget(self.showDeformation, 4, 0) # flagVbox.addWidget(self.showTitle, 4, 1) # flagVbox.addWidget(self.showLegend2, 4, 2) # lastCalibVbox = QGridLayout() # self.latCalibGroupBox.setLayout(lastCalibVbox) # lastCalibVbox.addWidget(self.frLabel, 0, 1, 1, 1) # lastCalibVbox.addWidget(self.prl2Label, 0, 2, 1, 1) # lastCalibVbox.addWidget(self.zero2Label, 0, 3, 1, 1) # lastCalibVbox.addWidget(self.startLabel2, 1, 0, 1, 1) # lastCalibVbox.addWidget(self.frictionRange1, 1, 1, 1, 1) # lastCalibVbox.addWidget(self.endLabel2, 2, 0, 1, 1) # lastCalibVbox.addWidget(self.frictionRange2, 2, 1, 1, 1) # lastCalibVbox.addWidget(self.prl2Range1, 1, 2, 1, 1) # lastCalibVbox.addWidget(self.prl2Range2, 2, 2, 1, 1) # lastCalibVbox.addWidget(self.zero2Range1, 1, 3, 1, 1) # lastCalibVbox.addWidget(self.zero2Range2, 2, 3, 1, 1) # lastCalibVbox.addWidget(self.latCTlabel, 3, 0, 1, 1) # lastCalibVbox.addWidget(self.latCrossTalk, 3, 1, 1, 1) # fittingVbox = QGridLayout() # self.fittingGroupBox.setLayout(fittingVbox) # fittingVbox.addWidget(self.startFitLabel, 0, 0, 1, 1) # fittingVbox.addWidget(self.endFitLabel, 1, 0, 1, 1) # fittingVbox.addWidget(self.fitStart, 0, 1, 1, 1) # fittingVbox.addWidget(self.fitStop, 1, 1, 1, 1) # fittingVbox.addWidget(self.xFitLabel, 0, 2, 1, 1) # fittingVbox.addWidget(self.yFitLabel, 1, 2, 1, 1) # fittingVbox.addWidget(self.xFit, 0, 3, 1, 1) # fittingVbox.addWidget(self.yFit, 1, 3, 1, 1) # fittingVbox.addWidget(self.fitPosLabel, 0, 4, 1, 1) # fittingVbox.addWidget(self.fitPos, 0, 5, 1, 1) # fittingVbox.addWidget(self.showFitEq, 1, 4, 1, 2) def filter_change(self): if self.filter_wind.value() % 2 == 0: #make sure its odd self.filter_wind.blockSignals(True) self.filter_wind.setValue(self.filter_wind.value() + 1) self.filter_wind.blockSignals(False) self.update_dict() # def update_range(self): # key = self.roiChoice.currentText() # if key not in self.rangeDict.keys(): # key = "Default" # self.zeroRange1.blockSignals(True) # self.zeroRange1.setValue(self.rangeDict[key][0][0]) # self.zeroRange1.blockSignals(False) # self.zeroRange2.blockSignals(True) # self.zeroRange2.setValue(self.rangeDict[key][0][1]) # self.zeroRange2.blockSignals(False) # self.adhRange1.blockSignals(True) # self.adhRange1.setValue(self.rangeDict[key][1][0]) # self.adhRange1.blockSignals(False) # self.adhRange2.blockSignals(True) # self.adhRange2.setValue(self.rangeDict[key][1][1]) # self.adhRange2.blockSignals(False) # self.prl1Range1.blockSignals(True) # self.prl1Range1.setValue(self.rangeDict[key][2][0]) # self.prl1Range1.blockSignals(False) # self.prl1Range2.blockSignals(True) # self.prl1Range2.setValue(self.rangeDict[key][2][1]) # self.prl1Range2.blockSignals(False) # self.frictionRange1.blockSignals(True) # self.frictionRange1.setValue(self.rangeDict[key][3][0]) # self.frictionRange1.blockSignals(False) # self.frictionRange2.blockSignals(True) # self.frictionRange2.setValue(self.rangeDict[key][3][1]) # self.frictionRange2.blockSignals(False) # self.prl2Range1.blockSignals(True) # self.prl2Range1.setValue(self.rangeDict[key][4][0]) # self.prl2Range1.blockSignals(False) # self.prl2Range2.blockSignals(True) # self.prl2Range2.setValue(self.rangeDict[key][4][1]) # self.prl2Range2.blockSignals(False) # self.zero2Range1.blockSignals(True) # self.zero2Range1.setValue(self.rangeDict[key][5][0]) # self.zero2Range1.blockSignals(False) # self.zero2Range2.blockSignals(True) # self.zero2Range2.setValue(self.rangeDict[key][5][1]) # self.zero2Range2.blockSignals(False) def update_widgets(self): self.forceBtn.setText( self.dataChoiceDict[self.dataChoice.currentText()] + " Range") # range_dict = self.dataAnalDict['force settings'][self.roiChoice.currentText()][self.dataChoice.currentText()] range_dict = self.dataAnalDict[self.dataChoice.currentText( )]["ranges"][self.roiChoice.currentText()] transform_dict = self.dataAnalDict[ self.dataChoice.currentText()]["transform"] self.zeroLabel.blockSignals(True) self.zeroLabel.setText(range_dict["Zero"]) self.zeroLabel.blockSignals(False) self.forceLabel.blockSignals(True) self.forceLabel.setText(range_dict["Force"]) self.forceLabel.blockSignals(False) self.preloadLabel.blockSignals(True) self.preloadLabel.setText(range_dict["Preload"]) self.preloadLabel.blockSignals(False) self.filter.blockSignals(True) self.filter.setChecked(transform_dict["Filter"]) self.filter.blockSignals(False) self.filter_wind.blockSignals(True) self.filter_wind.setValue(transform_dict["Filter window"]) self.filter_wind.blockSignals(False) self.filter_poly.blockSignals(True) self.filter_poly.setValue(transform_dict["Filter order"]) self.filter_poly.blockSignals(False) self.zero_subtract.blockSignals(True) self.zero_subtract.setChecked(transform_dict["Zero subtract"]) self.zero_subtract.blockSignals(False) self.crossTalk.blockSignals(True) self.crossTalk.setValue(transform_dict["Cross Talk"]) self.crossTalk.blockSignals(False) def update_dict(self): # range_dict = self.dataAnalDict['force settings'][self.roiChoice.currentText()][self.dataChoice.currentText()] range_dict = self.dataAnalDict[self.dataChoice.currentText( )]["ranges"][self.roiChoice.currentText()] transform_dict = self.dataAnalDict[ self.dataChoice.currentText()]["transform"] range_dict["Zero"] = self.zeroLabel.text() range_dict["Force"] = self.forceLabel.text() range_dict["Preload"] = self.preloadLabel.text() transform_dict["Filter"] = self.filter.isChecked() transform_dict["Filter window"] = self.filter_wind.value() transform_dict["Filter order"] = self.filter_poly.value() transform_dict["Zero subtract"] = self.zero_subtract.isChecked() transform_dict["Cross Talk"] = self.crossTalk.value() # self.rangeDict[self.roiChoice.currentText()] = [[self.zeroRange1.value(), # self.zeroRange2.value()], # [self.adhRange1.value(), # self.adhRange2.value()], # [self.prl1Range1.value(), # self.prl1Range2.value()], # [self.frictionRange1.value(), # self.frictionRange2.value()], # [self.prl2Range1.value(), # self.prl2Range2.value()], # [self.zero2Range1.value(), # self.zero2Range2.value()]] logging.debug('%s', self.dataAnalDict) def show_window(self): #show window # self.update_range() self.update_dict() self.show()
class myQLineEditPlus(QWidget): changed = pyqtSignal() def __init__(self, parent=None): QWidget.__init__(self) self.parent = parent self.lbl = QLabel(self) self.chk = QCheckBox(self) #self.chk.setFocusPolicy(Qt.NoFocus) self.txt = QLineEdit(self) self.lblSuffix = QLabel(self) self.lay = QHBoxLayout(self) self.lay.addWidget(self.lbl) self.lay.addWidget(self.chk) self.lay.addWidget(self.txt) self.lay.addWidget(self.lblSuffix) self.setLayout(self.lay) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) self.lbl.setSizePolicy(sizePolicy) self.setSuffix("") self.setValue(None) self.setLabel(self.tr("Add a value")) self.chk.stateChanged.connect(self.on_chk_stateChanged) self.txt.textChanged.connect(self.on_textChanged) self.txt.setMaxLength(30) self.txt.setAlignment(Qt.AlignRight) ## Functión to get the suffix in a label next to qlineedit def getSuffix(self): return self.lblSuffix.text() ## Functión to set the suffix in a label next to qlineedit def setSuffix(self, value): if value == None or value == "": self.lblSuffix.hide() else: self.lblSuffix.show() self.lblSuffix.setText(value) ## Functión to get the label next to qlineedit def getLabel(self): return self.lbl.text() ## Functión to set the label next to qlineedit def setLabel(self, value): value = "" if value == None else value if value == "": self.lbl.hide() else: self.lbl.show() self.lbl.setText(value) def value(self): if self.chk.isChecked() == False: return None if self.isNumber(): return Decimal(self.txt.text()) return None def setValue(self, v): self.txt.blockSignals(True) if v == None: self.chk.setCheckState(Qt.Unchecked) self.txt.setEnabled(False) self.txt.setText("") else: self.chk.setCheckState(Qt.Checked) self.txt.setEnabled(True) self.txt.setText(str(v)) self.txt.blockSignals(False) self.changed.emit() def on_chk_stateChanged(self, state): if state == Qt.Unchecked: self.setValue(None) else: self.setValue("0") self.chk.setFocusPolicy(Qt.NoFocus) self.txt.setFocus() self.txt.selectAll() def isChecked(self): return self.chk.isChecked() def isValid(self): if self.isNumber() or self.chk.isChecked() == False: return True else: return False def isNumber(self): try: Decimal(self.txt.text()) return True except: return False @pyqtSlot(str) def on_textChanged(self, text): pos = self.txt.cursorPosition() text = text.replace(",", ".") text = text.replace("e", "0") #Avoids scientific numbers self.setValue(text) if self.isChecked() == False: self.txt.setStyleSheet( "QLineEdit { background-color: rgb(239, 239, 239); }") elif self.isNumber(): self.txt.setStyleSheet( "QLineEdit { background-color: rgb(255, 255, 255); }") else: self.txt.setStyleSheet( "QLineEdit { background-color: rgb(255, 182, 182); }") self.txt.setCursorPosition(pos) def setMandatory(self, b): if b == True: self.chk.hide() self.chk.setCheckState(Qt.Checked) #self.txt.setText("0") #self.txt.setEnabled(True) else: self.chk.show()
class CaesarCipherDialog(QDialog): def __init__(self, input, callback): super(CaesarCipherDialog, self).__init__() main_layout = QVBoxLayout() main_layout.addWidget(self._init_editor_frame()) main_layout.addWidget(self._init_button_box()) self.setLayout(main_layout) self.setWindowIcon(qtawesome.icon("fa.edit")) self.setWindowTitle("Caesar Cipher") self._setup_shortcuts() self._input = input self._text_area.setPlainText(self._input) self._callback = callback def _setup_shortcuts(self): ctrl_return_shortcut = QShortcut( QKeySequence(QtCore.Qt.CTRL + QtCore.Qt.Key_Return), self) ctrl_return_shortcut.activated.connect(self._accept) alt_return_shortcut = QShortcut( QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_Return), self) alt_return_shortcut.activated.connect(self._accept) alt_o_shortcut = QShortcut( QKeySequence(QtCore.Qt.ALT + QtCore.Qt.Key_O), self) alt_o_shortcut.activated.connect(self._accept) def _init_button_box(self): button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._accept) button_box.rejected.connect(self.reject) return button_box def _init_editor_frame(self): main_frame = QFrame() main_frame_layout = QVBoxLayout() slider_frame = self._init_slider_frame() main_frame_layout.addWidget(slider_frame) self._text_area = QPlainTextEdit() self._text_area.setReadOnly(True) self._text_area.setFixedHeight(126) main_frame_layout.addWidget(self._text_area) main_frame.setLayout(main_frame_layout) return main_frame def _init_slider_frame(self): slider_frame = QFrame() slider_frame_layout = QHBoxLayout() self._shift_slider = QSlider(QtCore.Qt.Horizontal) self._shift_slider.setMinimum(0) self._shift_slider.setMaximum(26) self._shift_slider.setValue(0) self._shift_slider.valueChanged.connect(self._shift_slider_changed) slider_frame_layout.addWidget(self._shift_slider) self._shift_text = QLineEdit() self._shift_text.setText("0") self._shift_text.setFixedWidth(30) self._shift_text.setValidator(QIntValidator(0, 26)) self._shift_text.textChanged.connect(self._shift_text_changed) slider_frame_layout.addWidget(self._shift_text) slider_frame.setLayout(slider_frame_layout) return slider_frame def _shift_slider_changed(self, shift): if not shift: shift = 0 self._shift_changed(shift) def _shift_text_changed(self, shift): if not shift: shift = 0 self._shift_changed(int(shift)) def _shift_changed(self, shift): self._shift_text.blockSignals(True) self._shift_slider.blockSignals(True) self._shift_slider.setValue(shift) self._shift_text.setText(str(shift)) self._text_area.setPlainText(self._callback(self._input, shift)) self._shift_slider.blockSignals(False) self._shift_text.blockSignals(False) def _accept(self): self.accept() def getShift(self): return self._shift_slider.value()
class iv(QMainWindow): zoom_factor = 1.1 x_zoom = True y_zoom = True x_stop_at_orig = True y_stop_at_orig = True def __init__(self, *args, **kwargs): app = QtCore.QCoreApplication.instance() if app is None: app = QApplication(['']) QMainWindow.__init__(self, parent=None) timestamp = datetime.now().strftime("%y%m%d_%H%M%S") self.setWindowTitle('iv ' + timestamp) shell = get_ipython() shell.magic('%matplotlib qt') # store list of input images if len(args) == 1 and isinstance(args[0], torch.Tensor): # handle torch.Tensor input if args[0].ndim <= 3: self.images = [args[0].detach().cpu().numpy()] elif args[0].ndim == 4: # probably a torch tensor with dimensions [batch, channels, y, x] self.images = [[]] * args[0].shape[0] tmp = args[0].detach().cpu().numpy().transpose((2, 3, 1, 0)) for imind in range(tmp.shape[3]): self.images[imind] = tmp[:, :, :, imind] del tmp else: raise Exception('torch tensors can at most have 4 dimensions') elif len(args) == 1 and isinstance(args[0], np.ndarray) and len( args[0].shape) == 4: # handle 4D numpy.ndarray input by slicing in 4th dimension self.images = [[]] * args[0].shape[3] for imind in range(args[0].shape[3]): self.images[imind] = args[0][:, :, :, imind] elif len(args) == 1 and (isinstance(args[0], list) or isinstance(args[0], tuple)): self.images = list(args[0]) else: self.images = list(args) for imind in range(len(self.images)): if isinstance(self.images[imind], torch.Tensor): self.images[imind] = self.images[imind].detach().cpu().numpy() if self.images[imind].ndim == 4: # probably a torch tensor with dimensions [batch, channels, y, x] self.images[imind] = self.images[imind].transpose( (2, 3, 1, 0)) elif self.images[imind].ndim > 4: raise Exception( 'torch tensors can at most have 4 dimensions') self.images[imind] = np.atleast_3d(self.images[imind]) if self.images[imind].shape[2] != 1 and self.images[imind].shape[ 2] != 3: if self.images[imind].ndim == 4: self.images[imind] = self.images[imind].transpose( (2, 3, 1, 0)) self.imind = 0 # currently selected image self.nims = len(self.images) self.scale = 1. self.gamma = 1. self.offset = 0. self.autoscalePrctile = 0.1 self.autoscaleUsePrctiles = True self.autoscaleOnChange = False self.autoscalePerImg = False self.collageActive = False self.collageTranspose = False self.collageTransposeIms = False self.collage_nc = int(np.ceil(np.sqrt(self.nims))) self.collage_nr = int(np.ceil(self.nims / self.collage_nc)) self.collage_border_width = 0 self.collage_border_value = 0. self.crop = kwargs.get('crop', False) self.crop_global = kwargs.get('crop_global', True) self.zoom_factor = 1.1 self.x_zoom = True self.y_zoom = True self.x_stop_at_orig = True self.y_stop_at_orig = True self.annotate = False self.font_size = 12 self.crop_bounds() self.initUI() self.ax.set_xticks([]) self.ax.set_yticks([]) #plt.tight_layout() self.updateImage() if self.autoscaleOnChange: self.autoscale() self.cur_xlims = self.ih.axes.axis()[0:2] self.cur_ylims = self.ih.axes.axis()[2:] self.mouse_down = 0 self.x_start = 0 self.y_start = 0 self.cid = self.fig.canvas.mpl_connect('button_press_event', self.onclick) self.cid = self.fig.canvas.mpl_connect('button_release_event', self.onrelease) self.cid = self.fig.canvas.mpl_connect('motion_notify_event', self.onmotion) self.cid = self.fig.canvas.mpl_connect( 'key_press_event', self.keyPressEvent) #onkeypress) self.cid = self.fig.canvas.mpl_connect( 'key_release_event', self.keyReleaseEvent) #onkeyrelease) self.cid = self.fig.canvas.mpl_connect('scroll_event', self.onscroll) self.alt = False self.control = False self.shift = False self.prev_delta_x = 0 self.prev_delta_y = 0 #plt.show(block=True) #plt.pause(10) #plt.show(block=False) self.setWindowModality(QtCore.Qt.WindowModal) self.show() def crop_bounds(self): # pre-compute cropping bounds (tight bounding box around non-zero pixels) nzs = [np.where(np.sum(im, axis=2) > 0) for im in self.images] self.xmins = [np.min(nz[1]) if len(nz[1]) else 0 for nz in nzs] self.xmaxs = [ np.max(nz[1]) + 1 if len(nz[1]) else im.shape[1] for nz, im in zip(nzs, self.images) ] # +1 to allow easier indexing self.ymins = [np.min(nz[0]) if len(nz[0]) else 0 for nz in nzs] self.ymaxs = [ np.max(nz[0]) + 1 if len(nz[0]) else im.shape[0] for nz, im in zip(nzs, self.images) ] # +1 to allow easier indexing if self.crop_global: self.xmins = [np.min(self.xmins) for _ in self.xmins] self.xmaxs = [np.max(self.xmaxs) for _ in self.xmaxs] self.ymins = [np.min(self.ymins) for _ in self.ymins] self.ymaxs = [np.max(self.ymaxs) for _ in self.ymaxs] def initUI(self): #self.fig = plt.figure(figsize = (10, 10)) #self.ax = plt.axes([0,0,1,1])#, self.gs[0]) self.widget = QWidget() self.fig = Figure(dpi=100) self.canvas = FigureCanvas(self.fig) self.canvas.setParent(self.widget) #self.ax = Axes(fig=self.fig, rect=[0,0,1,1]) self.ax = self.fig.add_subplot(111) self.ax.set_position(Bbox([[0, 0], [1, 1]])) self.ax.set_anchor('NW') try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() self.uiLabelModifiers = QLabel('') self.uiLEScale = QLineEdit(str(self.scale)) self.uiLEScale.setMinimumWidth(200) self.uiLEScale.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLEScale)) self.uiLEGamma = QLineEdit(str(self.gamma)) self.uiLEGamma.setMinimumWidth(200) self.uiLEGamma.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLEGamma)) self.uiLEOffset = QLineEdit(str(self.offset)) self.uiLEOffset.setMinimumWidth(200) self.uiLEOffset.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLEOffset)) self.uiLEAutoscalePrctile = QLineEdit(str(self.autoscalePrctile)) self.uiLEAutoscalePrctile.setMinimumWidth(200) self.uiLEAutoscalePrctile.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLEAutoscalePrctile)) self.uiCBAutoscaleUsePrctiles = QCheckBox('use percentiles') self.uiCBAutoscaleUsePrctiles.setCheckState(self.autoscaleUsePrctiles) self.uiCBAutoscaleUsePrctiles.setTristate(False) self.uiCBAutoscaleUsePrctiles.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBAutoscaleUsePrctiles, state)) self.uiCBAutoscaleOnChange = QCheckBox('on change') self.uiCBAutoscaleOnChange.setCheckState(self.autoscaleOnChange) self.uiCBAutoscaleOnChange.setTristate(False) self.uiCBAutoscaleOnChange.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBAutoscaleOnChange, state)) if self.nims > 1: self.uiCBAutoscalePerImg = QCheckBox('per image') self.uiCBAutoscalePerImg.setCheckState(self.autoscalePerImg) self.uiCBAutoscalePerImg.setTristate(False) self.uiCBAutoscalePerImg.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBAutoscalePerImg, state)) self.uiCBCollageActive = QCheckBox('enable') self.uiCBCollageActive.setCheckState(self.collageActive) self.uiCBCollageActive.setTristate(False) self.uiCBCollageActive.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBCollageActive, state)) self.uiCBCollageTranspose = QCheckBox('transpose') self.uiCBCollageTranspose.setCheckState(self.collageTranspose) self.uiCBCollageTranspose.setTristate(False) self.uiCBCollageTranspose.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBCollageTranspose, state)) self.uiCBCollageTransposeIms = QCheckBox('transpose images') self.uiCBCollageTransposeIms.setCheckState( self.collageTransposeIms) self.uiCBCollageTransposeIms.setTristate(False) self.uiCBCollageTransposeIms.stateChanged.connect( lambda state: self.callbackCheckBox( self.uiCBCollageTransposeIms, state)) self.uiLECollageNr = QLineEdit(str(self.collage_nr)) self.uiLECollageNr.setMinimumWidth(200) self.uiLECollageNr.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLECollageNr)) self.uiLECollageNc = QLineEdit(str(self.collage_nc)) self.uiLECollageNc.setMinimumWidth(200) self.uiLECollageNc.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLECollageNc)) self.uiLECollageBW = QLineEdit(str(self.collage_border_width)) self.uiLECollageBW.setMinimumWidth(200) self.uiLECollageBW.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLECollageBW)) self.uiLECollageBV = QLineEdit(str(self.collage_border_value)) self.uiLECollageBV.setMinimumWidth(200) self.uiLECollageBV.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLECollageBV)) self.uiCBCrop = QCheckBox('enable') self.uiCBCrop.setCheckState(self.crop) self.uiCBCrop.setTristate(False) self.uiCBCrop.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBCrop, state)) self.uiCBCropGlobal = QCheckBox('enable') self.uiCBCropGlobal.setCheckState(self.crop_global) self.uiCBCropGlobal.setTristate(False) self.uiCBCropGlobal.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBCropGlobal, state)) self.uiCBAnnotate = QCheckBox('enable') self.uiCBAnnotate.setCheckState(self.annotate) self.uiCBAnnotate.setTristate(False) self.uiCBAnnotate.stateChanged.connect( lambda state: self.callbackCheckBox(self.uiCBAnnotate, state)) self.uiLEFontSize = QLineEdit(str(self.font_size)) self.uiLEFontSize.setMinimumWidth(200) self.uiLEFontSize.editingFinished.connect( lambda: self.callbackLineEdit(self.uiLEFontSize)) self.uiPBCopyClipboard = QPushButton('©') self.uiPBCopyClipboard.clicked.connect( lambda: self.callbackPushButton(self.uiPBCopyClipboard)) form = QFormLayout() form.addRow(QLabel('modifiers:'), self.uiLabelModifiers) form.addRow(QLabel('scale:'), self.uiLEScale) form.addRow(QLabel('gamma:'), self.uiLEGamma) form.addRow(QLabel('offset:'), self.uiLEOffset) form.addRow(QLabel('autoScale:'), self.uiCBAutoscaleUsePrctiles) form.addRow(QLabel('percentile:'), self.uiLEAutoscalePrctile) form.addRow(QLabel('autoScale:'), self.uiCBAutoscaleOnChange) if self.nims > 1: form.addRow(QLabel('autoScale:'), self.uiCBAutoscalePerImg) form.addRow(QLabel('collage:'), self.uiCBCollageActive) form.addRow(QLabel('collage:'), self.uiCBCollageTranspose) form.addRow(QLabel('collage:'), self.uiCBCollageTransposeIms) form.addRow(QLabel('collage #rows:'), self.uiLECollageNr) form.addRow(QLabel('collage #cols:'), self.uiLECollageNc) form.addRow(QLabel('collage #BW:'), self.uiLECollageBW) form.addRow(QLabel('collage #BV:'), self.uiLECollageBV) form.addRow(QLabel('crop:'), self.uiCBCrop) form.addRow(QLabel('crop global:'), self.uiCBCropGlobal) form.addRow(QLabel('annotate:'), self.uiCBAnnotate) form.addRow(QLabel('font size:'), self.uiLEFontSize) form_bottom = QFormLayout() form_bottom.addRow(self.uiPBCopyClipboard) vbox = QVBoxLayout() vbox.addLayout(form) vbox.addItem(QSpacerItem(1, 1, vPolicy=QSizePolicy.Expanding)) vbox.addLayout(form_bottom) hbox = QHBoxLayout() hbox.addWidget(self.canvas) hbox.addLayout(vbox) self.widget.setLayout(hbox) self.setCentralWidget(self.widget) # make image canvas expand with window sp = self.canvas.sizePolicy() sp.setHorizontalStretch(1) sp.setVerticalStretch(1) self.canvas.setSizePolicy(sp) self.ih = self.ax.imshow(np.zeros(self.get_img().shape[:2] + (3, )), origin='upper') self.ax.set_position(Bbox([[0, 0], [1, 1]])) try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() # keyboard shortcuts #scaleShortcut = QShortcut(QKeySequence('Ctrl+Shift+a'), self.widget) #scaleShortcut.activated.connect(self.autoscale) closeShortcut = QShortcut(QKeySequence('Escape'), self.widget) closeShortcut.activated.connect(self.close) QShortcut(QKeySequence('a'), self.widget).activated.connect(self.autoscale) QShortcut(QKeySequence('Shift+a'), self.widget).activated.connect( self.toggleautoscaleUsePrctiles) #@MyPyQtSlot("bool") def callbackLineEdit(self, ui): try: tmp = float(ui.text()) except: return if ui == self.uiLEScale: self.setScale(tmp) elif ui == self.uiLEGamma: self.setGamma(tmp) elif ui == self.uiLEOffset: self.setOffset(tmp) elif ui == self.uiLEAutoscalePrctile: self.autoscalePrctile = tmp self.autoscale() elif ui == self.uiLECollageNr: self.collage_nr = int(tmp) self.collage() elif ui == self.uiLECollageNc: self.collage_nc = int(tmp) self.collage() elif ui == self.uiLECollageBW: self.collage_border_width = int(tmp) self.collage() elif ui == self.uiLECollageBV: self.collage_border_value = float(tmp) self.collage() elif ui == self.uiLEFontSize: self.font_size = int(tmp) self.updateImage() #@MyPyQtSlot("bool") def callbackCheckBox(self, ui, state): if ui == self.uiCBAutoscaleUsePrctiles: self.autoscaleUsePrctiles = bool(state) if self.autoscaleOnChange: self.autoscale() elif ui == self.uiCBAutoscaleOnChange: self.autoscaleOnChange = bool(state) if self.autoscaleOnChange: self.autoscale() elif ui == self.uiCBAutoscalePerImg: self.autoscalePerImg = bool(state) self.autoscale() elif ui == self.uiCBCollageActive: self.collageActive = bool(state) self.updateImage() elif ui == self.uiCBCollageTranspose: self.collageTranspose = bool(state) self.updateImage() elif ui == self.uiCBCollageTransposeIms: self.collageTransposeIms = bool(state) self.updateImage() elif ui == self.uiCBCrop: self.crop = bool(state) self.updateImage() elif ui == self.uiCBCropGlobal: self.crop_global = bool(state) self.crop_bounds() self.updateImage() elif ui == self.uiCBAnnotate: self.annotate = bool(state) self.updateImage() def callbackPushButton(self, ui): if ui == self.uiPBCopyClipboard: self.copy_to_clipboard() ''' @MyPyQtSlot() def slot_text(self):#, ui=None): ui = self.uiLEScale if ui == self.uiLEScale: print('scale: ' + str(self.scale)) tmp = self.scale try: tmp = float(self.uiLEScale.text()) except ValueError: print('error') self.uiLEScale.setText(str(self.scale)) self.scale = tmp self.updateImage() elif ui == self.uiLEGamma: print('gamma') elif ui == self.uiLEOffset: print('offset') def on_draw(self): """ Redraws the figure """ #self.axes.grid(self.grid_cb.isChecked()) self.canvas.draw() ''' def print_usage(self): print(' ') print('hotkeys: ') print('a: trigger autoscale') print('A: toggle autoscale of [min, max] or ') print(' [prctile_low, prctile_high] -> [0, 1], ') print(' prctiles can be changed via ctrl+shift+wheel') print('c: toggle autoscale on image change') print('G: reset gamma to 1') print('L: create collage by arranging all images in a ') print(' rectangular manner') print('O: reset offset to 0') print('p: toggle per image auto scale limit computations ') print(' (vs. globally over all images)') print('S: reset scale to 1') print('Z: reset zoom to 100%') print('left / right: switch to next / previous image') print('page down / up: go through images in ~10% steps') print('') print('wheel: zoom in / out (inside image axes)') print('wheel: switch to next / previous image') print(' (outside image axes)') print('ctrl + wheel: scale up / down') print('shift + wheel: gamma up / down') print('ctrl + shift + wheel: increase / decrease autoscale') print(' percentiles') print('left mouse dragged: pan image') print('') def get_img(self, i=None): if i is None: i = self.imind im = self.images[i] if self.crop: im = im[self.ymins[i]:self.ymaxs[i], self.xmins[i]:self.xmaxs[i], :] if self.annotate: from pytb.utils import annotate_image im = annotate_image(im, str(i), font_size=self.font_size) return im def get_imgs(self): return [self.get_img(ind) for ind in range(len(self.images))] def copy_to_clipboard(self): from PyQt5.Qt import QImage im = (255 * self.ih.get_array()).astype(np.uint8) h, w, nc = im.shape[:3] im = QImage(im.tobytes(), w, h, nc * w, QImage.Format_RGB888) c = QApplication.clipboard() c.setImage(im) def autoscale(self): # autoscale between user-selected percentiles if self.autoscaleUsePrctiles: if self.autoscalePerImg: lower, upper = np.percentile( self.get_img(), (self.autoscalePrctile, 100 - self.autoscalePrctile)) else: limits = [ np.percentile( image, (self.autoscalePrctile, 100 - self.autoscalePrctile)) for image in self.get_imgs() ] lower = np.min([lims[0] for lims in limits]) upper = np.max([lims[1] for lims in limits]) else: if self.autoscalePerImg: lower = np.min(self.get_img()) upper = np.max(self.get_img()) else: lower = np.min([np.min(image) for image in self.get_imgs()]) upper = np.max([np.max(image) for image in self.get_imgs()]) self.setOffset(lower, False) self.setScale(1. / (upper - lower), True) def toggleautoscaleUsePrctiles(self): self.autoscaleUsePrctiles = not self.autoscaleUsePrctiles self.autoscale() def collage(self): if self.collage_nr * self.collage_nc < self.nims: nc = int(np.ceil(np.sqrt(self.nims))) nr = int(np.ceil(self.nims / nc)) self.collage_nr = nr self.collage_nc = nc self.uiLECollageNr.blockSignals(True) self.uiLECollageNc.blockSignals(True) self.uiLECollageNr.setText(str(nr)) self.uiLECollageNc.setText(str(nc)) self.uiLECollageNr.blockSignals(False) self.uiLECollageNc.blockSignals(False) else: nc = self.collage_nc nr = self.collage_nr # pad array so it matches the product nc * nr padding = nc * nr - self.nims ims = self.get_imgs() h = np.max([im.shape[0] for im in ims]) w = np.max([im.shape[1] for im in ims]) numChans = np.max([im.shape[2] for im in ims]) ims = [ pad(im, new_width=w, new_height=h, new_num_channels=numChans) for im in ims ] ims += [np.zeros((h, w, numChans))] * padding coll = np.stack(ims, axis=3) coll = np.reshape(coll, (h, w, numChans, nc, nr)) # 0 1 2 3 4 # y, x, ch, co, ro if self.collage_border_width: # pad each patch by border if requested coll = np.append( coll, self.collage_border_value * np.ones((self.collage_border_width, ) + coll.shape[1:5]), axis=0) coll = np.append( coll, self.collage_border_value * np.ones((coll.shape[0], self.collage_border_width) + coll.shape[2:5]), axis=1) if self.collageTranspose: nim0 = nr nim1 = nc if self.collageTransposeIms: dim0 = w dim1 = h # nr w nc h ch coll = np.transpose(coll, (4, 1, 3, 0, 2)) else: dim0 = h dim1 = w # nr h nc w ch coll = np.transpose(coll, (4, 0, 3, 1, 2)) else: nim0 = nc nim1 = nr if self.collageTransposeIms: dim0 = w dim1 = h # nc w nr h ch coll = np.transpose(coll, (3, 1, 4, 0, 2)) else: dim0 = h dim1 = w # nc h nr w ch coll = np.transpose(coll, (3, 0, 4, 1, 2)) coll = np.reshape( coll, ((dim0 + self.collage_border_width) * nim0, (dim1 + self.collage_border_width) * nim1, numChans)) #self.ih.set_data(self.tonemap(coll)) self.ax.clear() self.ih = self.ax.imshow(self.tonemap(coll), origin='upper') height, width = self.ih.get_size() lims = (-0.5, width - 0.5, -0.5, height - 0.5) self.ax.set(xlim=lims[0:2], ylim=lims[2:4]) try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() self.fig.canvas.draw() def switch_to_single_image(self): if self.collageActive: self.ax.clear() self.ih = self.ax.imshow(np.zeros(self.get_img().shape[:3]), origin='upper') self.collageActive = False def reset_zoom(self): height, width = self.ih.get_size() lims = (-0.5, width - 0.5, -0.5, height - 0.5) self.ih.axes.axis(lims) self.ax.set_position(Bbox([[0, 0], [1, 1]])) try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() self.fig.canvas.draw() def zoom(self, pos, factor): lims = self.ih.axes.axis() xlim = lims[0:2] ylim = lims[2:] # compute interval lengths left, right, below and above cursor left = pos[0] - xlim[0] right = xlim[1] - pos[0] below = pos[1] - ylim[0] above = ylim[1] - pos[1] # zoom in or out if self.x_zoom: xlim = [pos[0] - factor * left, pos[0] + factor * right] if self.y_zoom: ylim = [pos[1] - factor * below, pos[1] + factor * above] # no zooming out beyond original zoom level height, width = self.ih.get_size() if self.x_stop_at_orig: xlim = [ np.maximum(-0.5, xlim[0]), np.minimum(width - 0.5, xlim[1]) ] if self.y_stop_at_orig: ylim = [ np.maximum(-0.5, ylim[0]), np.minimum(height - 0.5, ylim[1]) ] # update axes if xlim[0] != xlim[1] and ylim[0] != ylim[1]: lims = (xlim[0], xlim[1], ylim[0], ylim[1]) self.ih.axes.axis(lims) try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() self.ax.set_position(Bbox([[0, 0], [1, 1]])) self.fig.canvas.draw() return def tonemap(self, im): if isinstance(im, np.matrix): im = np.array(im) if im.shape[2] == 1: im = np.repeat(im, 3, axis=2) elif im.shape[2] == 2: im = np.concatenate( (im, np.zeros((im.shape[0], im.shape[1], 2), dtype=im.dtype)), axis=2) elif im.shape[2] != 3: # project to RGB raise Exception('spectral to RGB conversion not implemented') return np.power( np.maximum(0., np.minimum(1., (im - self.offset) * self.scale)), 1. / self.gamma) def updateImage(self): if self.collageActive: self.collage() else: if self.nims > 1: self.uiCBCollageActive.blockSignals(True) self.uiCBCollageActive.setChecked(False) self.uiCBCollageActive.blockSignals(False) height, width = self.ih.get_size() im = self.get_img() if height != im.shape[0] or width != im.shape[1]: # image size changed, create new axes self.ax.clear() self.ih = self.ax.imshow(self.tonemap(im)) else: self.ih.set_data(self.tonemap(im)) height, width = self.ih.get_size() lims = (-0.5, width - 0.5, -0.5, height - 0.5) self.ax.set(xlim=lims[0:2], ylim=lims[2:4]) try: self.ax.get_yaxis().set_inverted(True) except Exception: self.ax.invert_yaxis() self.fig.canvas.draw() def setScale(self, scale, update=True): self.scale = scale self.uiLEScale.setText(str(self.scale)) if update: self.updateImage() def setGamma(self, gamma, update=True): self.gamma = gamma self.uiLEGamma.setText(str(self.gamma)) if update: self.updateImage() def setOffset(self, offset, update=True): self.offset = offset self.uiLEOffset.setText(str(self.offset)) if update: self.updateImage() def onclick(self, event): if event.dblclick: self.reset_zoom() self.mouse_down ^= event.button elif event.inaxes: self.x_start = event.xdata self.y_start = event.ydata self.prev_delta_x = 0 self.prev_delta_y = 0 self.cur_xlims = self.ih.axes.axis()[0:2] self.cur_ylims = self.ih.axes.axis()[2:] self.mouse_down |= event.button def onrelease(self, event): self.mouse_down ^= event.button def onmotion(self, event): if self.mouse_down == 1 and event.inaxes: delta_x = self.x_start - event.xdata delta_y = self.y_start - event.ydata self.ih.axes.axis( (self.cur_xlims[0] + delta_x, self.cur_xlims[1] + delta_x, self.cur_ylims[0] + delta_y, self.cur_ylims[1] + delta_y)) self.fig.canvas.draw() self.x_start += (delta_x - self.prev_delta_x) self.y_start += (delta_y - self.prev_delta_y) self.prev_delta_x = delta_x self.prev_delta_y = delta_y def keyPressEvent(self, event): #def onkeypress(self, event): key = event.key() mod = event.modifiers() if key == Qt.Key_Question: # ? self.print_usage() elif key == Qt.Key_A: # a # trigger autoscale self.autoscale() return elif key == Qt.Key_A and mod == Qt.Key_Shift: # A # toggle autoscale between user-selected percentiles or min-max self.autoscaleUsePrctiles = not self.autoscaleUsePrctiles self.autoscale() return elif key == Qt.Key_C: # toggle on-change autoscale self.autoscaleOnChange = not self.autoscaleOnChange print('on-change autoscaling is %s' % ('on' if self.autoscaleOnChange else 'off')) elif key == Qt.Key_G: self.gamma = 1. elif key == Qt.Key_L: # update axes for single image dimensions if self.collageActive: self.switch_to_single_image() else: # toggle showing collage self.collageActive = not self.collageActive # also disable per-image scaling limit computation self.autoscalePerImg = not self.autoscalePerImg elif key == Qt.Key_O: self.offset = 0. elif key == Qt.Key_P: self.autoscalePerImg = not self.autoscalePerImg print('per-image scaling is %s' % ('on' if self.autoscalePerImg else 'off')) self.autoscale() elif key == Qt.Key_S: self.scale = 1. elif key == Qt.Key_Z: # reset zoom self.ih.axes.autoscale(True) elif key == Qt.Key_Alt: self.alt = True self.uiLabelModifiers.setText('alt: %d, ctrl: %d, shift: %d' % (self.alt, self.control, self.shift)) return elif key == Qt.Key_Control: self.control = True self.uiLabelModifiers.setText('alt: %d, ctrl: %d, shift: %d' % (self.alt, self.control, self.shift)) return elif key == Qt.Key_Shift: self.shift = True self.uiLabelModifiers.setText('alt: %d, ctrl: %d, shift: %d' % (self.alt, self.control, self.shift)) return elif key == Qt.Key_Left: self.switch_to_single_image() self.imind = np.mod(self.imind - 1, self.nims) print('image %d / %d' % (self.imind + 1, self.nims)) if self.autoscaleOnChange: self.autoscale() return elif key == Qt.Key_Right: self.switch_to_single_image() self.imind = np.mod(self.imind + 1, self.nims) print('image %d / %d' % (self.imind + 1, self.nims)) if self.autoscaleOnChange: self.autoscale() return else: return self.updateImage() def keyReleaseEvent(self, event): #def onkeyrelease(self, event): key = event.key() if key == Qt.Key_Alt: self.alt = False elif key == Qt.Key_Control: self.control = False elif key == Qt.Key_Shift: self.shift = False self.uiLabelModifiers.setText('alt: %d, ctrl: %d, shift: %d' % (self.alt, self.control, self.shift)) def onscroll(self, event): if self.control and self.shift: # autoscale percentiles self.autoscalePrctile *= np.power(1.1, event.step) self.autoscalePrctile = np.minimum(100, self.autoscalePrctile) print('auto percentiles: [%3.5f, %3.5f]' % (self.autoscalePrctile, 100 - self.autoscalePrctile)) self.autoscaleUsePrctiles = True self.autoscale() elif self.control: # scale #self.setScale(self.scale * np.power(1.1, event.step)) self.setScale(self.scale * np.power(1.1, event.step)) elif self.shift: # gamma self.setGamma(self.gamma * np.power(1.1, event.step)) elif event.inaxes: # zoom when inside image axes factor = np.power(self.zoom_factor, -event.step) self.zoom([event.xdata, event.ydata], factor) return else: # scroll through images when outside of axes self.switch_to_single_image() self.imind = int(np.mod(self.imind - event.step, self.nims)) print('image %d / %d' % (self.imind + 1, self.nims)) if self.autoscaleOnChange: self.autoscale() return self.updateImage() def save(self, ofname): imageio.imwrite(ofname, np.array(self.ih.get_array()))
class QWatson(QWidget, QWatsonImportMixin, QWatsonProjectMixin, QWatsonActivityMixin): def __init__(self, config_dir=None, parent=None): super(QWatson, self).__init__(parent) self.setWindowIcon(icons.get_icon('master')) self.setWindowTitle(__namever__) self.setMinimumWidth(300) self.setWindowFlags(Qt.Window | Qt.WindowMinimizeButtonHint | Qt.WindowCloseButtonHint) if platform.system() == 'Windows': import ctypes myappid = __namever__ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( myappid) config_dir = (config_dir or os.environ.get('QWATSON_DIR') or click.get_app_dir('QWatson')) self.client = Watson(config_dir=config_dir) self.model = WatsonTableModel(self.client) self.setup_activity_overview() self.setup() if self.client.is_started: self.add_new_project(self.client.current['project']) self.stop_watson(tags=['error'], message="last session not closed correctly.") self.set_settings_from_index(-1) # ---- Setup layout def setup(self): """Setup the main widget.""" # Setup the stack widget. self.stackwidget = QStackedWidget() self.setup_activity_tracker() self.setup_datetime_input_dialog() self.setup_close_dialog() self.setup_del_project_dialog() self.setup_merge_project_dialog() self.setup_import_dialog() # Setup the main layout of the widget layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.stackwidget) def setup_close_dialog(self): """ Setup a dialog that is shown when closing QWatson while and activity is being tracked. """ self.close_dial = CloseDialog(parent=self) self.close_dial.register_dialog_to(self) def setup_datetime_input_dialog(self): """ Setup the dialog to ask the user to enter a datetime value for the starting time of the activity. """ self.datetime_input_dial = DateTimeInputDialog(parent=self) self.datetime_input_dial.register_dialog_to(self) # ---- Main interface def setup_activity_tracker(self): """Setup the widget used to start, track, and stop new activity.""" stopwatch = self.setup_stopwatch() managers = self.setup_watson_managers() statusbar = self.setup_statusbar() tracker = QWidget() layout = QVBoxLayout(tracker) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(stopwatch) layout.addWidget(managers) layout.addWidget(statusbar) layout.setStretch(1, 100) self.stackwidget.addWidget(tracker) # ---- Project, Tags and Comment def setup_watson_managers(self): """ Setup the embedded dialog to setup the current activity parameters. """ project_manager = self.setup_project_manager() self.tag_manager = TagLineEdit() self.tag_manager.setPlaceholderText("Tags (comma separated)") self.comment_manager = QLineEdit() self.comment_manager.setPlaceholderText("Comment") # ---- Setup the layout managers = ColoredFrame('light') layout = QGridLayout(managers) layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(project_manager, 0, 1) layout.addWidget(self.tag_manager, 1, 1) layout.addWidget(self.comment_manager, 2, 1) layout.addWidget(QLabel('project :'), 0, 0) layout.addWidget(QLabel('tags :'), 1, 0) layout.addWidget(QLabel('comment :'), 2, 0) return managers def set_settings_from_index(self, index): """ Load the settings in the manager from the data of the frame saved at index. """ if index is not None: try: frame = self.client.frames[index] self.project_manager.blockSignals(True) self.project_manager.setCurrentProject(frame.project) self.project_manager.blockSignals(False) self.tag_manager.blockSignals(True) self.tag_manager.set_tags(frame.tags) self.tag_manager.blockSignals(False) self.comment_manager.blockSignals(True) self.comment_manager.setText(frame.message) self.comment_manager.blockSignals(False) except IndexError: print("IndexError: list index out of range") # ---- Bottom Toolbar def setup_statusbar(self): """Setup the toolbar located at the bottom of the main widget.""" self.btn_report = QToolButtonSmall('note') self.btn_report.clicked.connect(self.overview_widg.show) self.btn_report.setToolTip("<b>Activity Overview</b><br><br>" "Open the activity overview window.") self.round_time_btn = DropDownToolButton(style='text_only') self.round_time_btn.addItems(list(ROUNDMIN.keys())) self.round_time_btn.setCurrentIndex(1) self.round_time_btn.setToolTip( "<b>Round Start and Stop</b><br><br>" "Round start and stop times to the nearest" " multiple of the selected factor.") self.btn_startfrom = DropDownToolButton(style='text_only') self.btn_startfrom.addItems( ['start from now', 'start from last', 'start from other']) self.btn_startfrom.setCurrentIndex(0) self.btn_startfrom.setToolTip( "<b>Start From</b><br><br>" "Set whether the current activity starts" " from the current time (now)," " from the stop time of the last logged activity (last)," " or from a user defined time (other).") # Setup the layout of the statusbar statusbar = ToolBarWidget('window') statusbar.setSpacing(0) statusbar.addWidget(self.round_time_btn) statusbar.addWidget(self.btn_startfrom) statusbar.addStretch(100) statusbar.addWidget(self.btn_report) statusbar.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)) return statusbar def roundTo(self): """ Return the start and stop rounding time factor, in minutes, that corresponds to the option selected in the round_time_btn. """ return ROUNDMIN[self.round_time_btn.text()] def startFrom(self): """ Return the mode to use to determine at what reference time the activity must refer to calculate its elapsed time. """ return STARTFROM[self.btn_startfrom.text()] # ---- Stackwidget handlers def addWidget(self, widget): """ Add a widget to the stackwidget and return the index where the widget was added. """ self.stackwidget.addWidget(widget) return self.stackwidget.count() - 1 def removeWidget(self, widget): """Remove a widget from the stackwidget.""" self.stackwidget.removeWidget(widget) def currentIndex(self): """Return the current index of the stackwidget.""" return self.stackwidget.currentIndex() def setCurrentIndex(self, index): """Set the current index of the stackwidget.""" self.stackwidget.setCurrentIndex(index) # ---- Stop, Start, and Cancel def setup_stopwatch(self): """ Setup the widget that contains a button to start/stop Watson and a digital clock that shows the elapsed amount of time since Watson was started. """ self.stopwatch = StopWatchWidget() self.stopwatch.sig_btn_start_clicked.connect(self.start_watson) self.stopwatch.sig_btn_stop_clicked.connect(self.stop_watson) self.stopwatch.sig_btn_cancel_clicked.connect(self.cancel_watson) return self.stopwatch def start_watson(self, start_time=None): """Start monitoring a new activity with the Watson client.""" if isinstance(start_time, arrow.Arrow): self.btn_startfrom.setEnabled(False) self.stopwatch.start(start_time) self.client.start(self.currentProject()) self.client._current['start'] = start_time else: frames = self.client.frames if self.startFrom() == 'now': self.start_watson(arrow.now()) elif self.startFrom() == 'last' and len(frames) > 0: self.start_watson(min(frames[-1].stop, arrow.now())) else: self.datetime_input_dial.show() def cancel_watson(self): """Cancel the Watson client if it is running and reset the UI.""" self.btn_startfrom.setEnabled(True) self.stopwatch.cancel() if self.client.is_started: self.client.cancel() def stop_watson(self, message=None, project=None, tags=None, round_to=None): """Stop Watson and update the table model.""" self.btn_startfrom.setEnabled(True) self.stopwatch.stop() self.client._current['message'] = \ self.comment_manager.text() if message is None else message self.client._current['project'] = \ self.currentProject() if project is None else project self.client._current['tags'] = \ self.tag_manager.tags if tags is None else tags self.model.beginInsertRows(QModelIndex(), len(self.client.frames), len(self.client.frames)) self.client.stop() # Round the start and stop times of the last added frame. round_frame_at(self.client, -1, self.roundTo() if round_to is None else round_to) self.client.save() self.model.endInsertRows() def closeEvent(self, event): """Qt method override.""" if self.client.is_started: self.close_dial.show() event.ignore() else: self.overview_widg.close() self.client.save() event.accept() print("QWatson is closed.\n")
class tabdemo(QTabWidget): def __init__(self, parent = None): super(tabdemo, self).__init__(parent) CurrentSoftwareVersion = '1.0.0' #Update as needed. Don't forget to update the Revision History as well self.AppName = "DF-DAQ - " + CurrentSoftwareVersion #Sets the name in the upper left hand corner of the GUI self.setWindowIcon(QIcon('DFIcon-01.ico')) #Sets the GUI Icon self.pressureOptions = ['PSI','HPA','KPA','MBAR','BAR','CMH2O','INH2O','MMHG'] self.oldRate = 0 self.dataOutputMultiplier = 1 self.outputType = '' self.tab1 = QWidget() #Define Tab1 self.tab2 = QWidget() #Define Tab2 self.tab3 = QWidget() #Define Tab3 self.tab4 = QWidget() #Define Tab4 self.addTab(self.tab1,"Acquisition") #Add Tab 1 to the QTabWidget #self.addTab(self.tab2,"Plot") #Add Tab 2 to the QTabWidget self.addTab(self.tab3,"Live Data") #Add Live Data Tab to QTabWidget #self.addTab(self.tab4,"Settings") #Add Settings Tab to QTabWidget self.tab1UI() #Run the Tab 1 setup self.tab2UI() self.tab3UI() self.tab4UI() self.DAQ = DF_DAQ() #Create a DF_DAQ object self.logMsg('Date: ' + datetime.datetime.now().strftime('%m-%d-%y'), False, 'black') #Write the Start Date to the Log self.logMsg('Software: ' + self.AppName, False, 'black') #Write the Software Version to the Log self.SearchCOMs() #Search the COM Ports for a Teensy self.setWindowTitle(self.AppName) self.setGeometry(650, 400, 610, 300) #Sets the X and Y location to start up in and the Width and Height of the GUI (px) self.setMinimumHeight(350) #Locks the height to 300 (px) self.setMinimumWidth(610) #Locks the minimum width to 600 (px) #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function searches the COM Ports and populates the COM Port # ComboBox with all avaliable Teensy Ports. #============================================================================== def SearchCOMs(self): COM = self.DAQ.findPort() #Use the DF_DAQ 'findPort' method to autodetect the Teensy self.COMDis.clear() #Reset the COM Port ComboBox if(len(COM) < 1): #Teensy not found, list of COM Ports returned print('No DF Hardware Connected') self.COMDis.addItem('NA') #Set the COM Port ComboBox to NA self.COMDis.setDisabled(True) #Disable the COM Port ComboBox self.FirmDis.setText('NA') #Set the Firmware LineEdit to NA self.DataOutput.setCurrentIndex(0) #Set the Output ComboBox to the first index self.HardChannels.setText('NA') #Set the Channels LineEdit to NA self.DataRate.setText('10') #Set the Rate LineEdit to 0 self.rateMax = 10 self.disableGUI(True) self.updateMult() #Update the Multiplier LineEdit self.logMsg('Please plug in the DroidForge hardware and refresh the COM', True, 'blue') else: #Teensy Found print('DF Hardware Connected') self.disableGUI(False) if (len(COM) == 1): #Only ONE DF Hardware Connected self.COMDis.addItem(str(COM[0])) #Add the prot to the COM Port ComboBox self.COMDis.setDisabled(True) #Disable the COM Port ComboBOx else: #Multiple DF Hardware Connected for i in range (0, len(COM)): #Loop Through all found COM Ports self.COMDis.addItem(str(COM[i])) #Add all items to the COM Port ComboBox self.COMDis.setDisabled(False) #Enable the COM Port ComboBox if (self.FirmDis.text() == 'NA'): print('No Firmware Detected') self.disableGUI(True) self.DataRate.setText('10') self.DataPrefix.setText("") self.removeTab(1) self.rateMax = 10 self.DataMultiplier.setText('NA') self.DataTime.setText('-') self.FileOutput.setText(str(os.getcwd()) + '\Temp.txt') #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function sets all the Hardware information with for the # selected COM Port. #============================================================================== def disableGUI(self, disabled): self.DataPrefix.setDisabled(disabled) self.DataRate.setDisabled(disabled) self.DataTime.setDisabled(disabled) self.DataOutput.setDisabled(disabled) self.plotStop.setDisabled(disabled) self.ButtonStart.setDisabled(disabled) self.plotWidth.setDisabled(disabled) def updateHardware(self): print('Updating Hardware') if(self.COMDis.currentText() != 'NA'): #Do nothing if there is no DF Hardware Detected self.logMsg('Searching for Attached Hardware...<br>', False, 'black') self.FirmDis.setText(self.DAQ.getFirmVer(str(self.COMDis.currentText()))) #Use the DF_DAQ 'getFirmVer' method to get the current firmware form the selected Teensy FirmStartup = self.DAQ.getSetup(str(self.COMDis.currentText())).split('-') self.logMsg('DF Board: ' + str(self.COMDis.currentText()), False, 'black') #Write the Teensy COM Port to the Log self.logMsg('Firmware: ' + self.FirmDis.text(), False, 'black') #Write the Firmware Version to the Log if(FirmStartup[0] == 'P'): self.DataOutput.clear() self.outputType = 'Pressure' for key in self.pressureOptions: self.DataOutput.addItem(str(key)) index = self.DataOutput.findText(FirmStartup[1], QtCore.Qt.MatchFixedString) if index >= 0: self.DataOutput.setCurrentIndex(index) self.logMsg('Hardware Type: Pressure', False, 'black') self.DataTime.setText('-') #Disable rate else: #Other Firmware Detected self.HardChannels.setText('NA') #Set the Channesl LineEdit to NA self.DataRate.setText('10') #Set the Rate LineEdit to 0 self.rateMax = 10 self.HardChannels.setText(FirmStartup[2]) self.rateMax = float(FirmStartup[3]) self.logMsg('Max Sample Rate: ' + str(self.rateMax), False, 'black') self.DataRate.setText(str(int(FirmStartup[4]))) #============================================================================== # Input Parameters: msg (Str), bold (Bool), color (Str) # Output Returns: none # # Description: This function takes in a mesage and writes it to the Log TextEdit. # If bold is True, the line is bolded. Color sets the color of the line, set to # 'black' for default. #============================================================================== def logMsg(self, msg, bold, color): boldStart = '<b>' #Bold Start Character boldEnd = '</b>' #Bold End Character colorStart = '<font color = ' + color + '>' #Color Start Character + color colorEnd = '</font>' #Color End Character if bold: msg = boldStart + str(msg) + boldEnd #Add Bold HTML Characters msg = colorStart + str(msg) + colorEnd #Add Color HTML Characters self.Log.moveCursor(QTextCursor.Start) #Move the cursor to the beginning of the TextEdit self.Log.insertHtml(str(datetime.datetime.now().strftime('%H:%M:%S - ')) + msg + '<br>') #Add the TimeStamp, Message Text and finally a return QApplication.processEvents() #Update the Log in the GUI #============================================================================== # Input Parameters: none # Output Returns: none (Tab 1 Layout) # # Description: This funcition defines the layout for the first Tab and associated # widgets. #============================================================================== def tab1UI(self): #Define all the different Layouts hlayout = QHBoxLayout() #Main Layer h2layout = QHBoxLayout() h4layout = QHBoxLayout() h6layout = QHBoxLayout() h7layout = QHBoxLayout() h8layout = QHBoxLayout() h9layout = QHBoxLayout() h10layout = QHBoxLayout() glayout = QGridLayout() glayout2 = QGridLayout() vlayout = QVBoxLayout() v2layout = QVBoxLayout() v3layout = QVBoxLayout() #Creat a font style to bold the headers headerFont = QFont() headerFont.setBold(True) #Run Time Log self.Log = QTextEdit() self.Log.setReadOnly(True) self.Log.setToolTip('Run Time Log') self.Log.setMinimumWidth(270) #COM Port self.COMDis = QComboBox() self.COMDis.setMinimumWidth(58) self.COMDis.setToolTip('COM Port') self.COMDis.currentIndexChanged.connect(self.updateHardware) #COM Refresh Button self.RefreshCOM = QPushButton() self.RefreshCOM.setMaximumWidth(22) self.RefreshCOM.setMaximumHeight(22) self.RefreshCOM.setToolTip('Refresh COM Port List') self.RefreshCOM.setIcon(QIcon('refresh.png')) self.RefreshCOM.clicked.connect(self.RefreshCOMs) #Firmware self.FirmDis = QLineEdit() self.FirmDis.setReadOnly(True) self.FirmDis.setDisabled(True) self.FirmDis.setMaximumWidth(80) self.FirmDis.setToolTip('Firmware on Device') #Channels self.HardChannels = QLineEdit() self.HardChannels.setMaximumWidth(80) self.HardChannels.setReadOnly(True) self.HardChannels.setDisabled(True) self.HardChannels.setToolTip('Number of channels on the hardware') #Start Button self.ButtonStart = QPushButton() self.ButtonStart.setText('Start') self.ButtonStart.setMaximumWidth(315) self.ButtonStart.clicked.connect(self.ToggleStartStop) self.ButtonStart.setToolTip('Start Recording Data') self.Start = False #Start boolean. (toggles when the Start Button is pressed) #Add a refresh button to the COM Port List h9layout.addWidget(self.COMDis) h9layout.addWidget(self.RefreshCOM) #Hardware Column - Grid Layout space = QSpacerItem(1, 1, QSizePolicy.Expanding, QSizePolicy.Expanding) glayout.addWidget(QLabel('COM Port'), 1, 3) glayout.addLayout(h9layout, 1, 5) glayout.addWidget(QLabel('Firmware'), 3, 3) glayout.addWidget(self.FirmDis, 3, 5) glayout.addWidget(QLabel('Channels'), 5, 3) glayout.addWidget(self.HardChannels, 5, 5) glayout.addItem(space, 7, 5) #Prefix self.DataPrefix = QLineEdit() self.DataPrefix.setText('POS') self.DataPrefix.setToolTip('Sets the prefix for the recorded data') self.DataPrefix.setMaximumWidth(80) self.OutputOptions = {'N/A':'1.0'} #Output self.DataOutput = QComboBox() self.DataOutput.setMaximumWidth(80) for key in self.OutputOptions: self.DataOutput.addItem(str(key)) self.DataOutput.model().sort(0) self.DataOutput.setToolTip('Sets the Multiplier for the incomming data') self.DataOutput.currentIndexChanged.connect(self.convertOutput) #Multiplier self.DataMultiplier = QLineEdit() self.DataMultiplier.setMaximumWidth(80) self.DataMultiplier.setToolTip('Sets a multiplier to conver the AD reading into Volts or Ohms') self.DataMultiplier.textChanged.connect(self.OnlyAllowInt) self.DataMultiplier.setDisabled(True) #Test Time self.DataTime = QLineEdit() self.DataTime.setMaximumWidth(55) self.DataTime.setText('-') self.DataTime.setToolTip('Length of recording (seconds). Set to 0 to disable') self.DataTime.textChanged.connect(self.OnlyAllowInt2) #Rate self.DataRate = QLineEdit() self.DataRate.setMaximumWidth(55) self.DataRate.setToolTip('Sets the rate of the firmware in Samples/Second') self.DataRate.setText('10') self.DataRate.textChanged.connect(self.SetRate) #Add a label to the Test Time Grid Point h7layout.addWidget(self.DataTime) h7layout.addWidget(QLabel('Sec')) #Add a label to the Rate Grid Point h8layout.addWidget(self.DataRate) h8layout.addWidget(QLabel('Hz')) h10layout.addStretch(1) # h10layout.addWidget(self.SetRateButton) #Customize Data - Grid Layout # glayout2.addWidget(QLabel('Prefix'), 1, 3) # glayout2.addWidget(self.DataPrefix , 1, 5) glayout2.addWidget(QLabel('Output'), 3, 3) glayout2.addWidget(self.DataOutput, 3, 5) glayout2.addWidget(QLabel('Multiplier'), 5, 3) glayout2.addWidget(self.DataMultiplier , 5, 5) glayout2.addWidget(QLabel('Test Time'), 7, 3) glayout2.addLayout(h7layout, 7, 5) glayout2.addWidget(QLabel('Rate'), 9, 3) glayout2.addLayout(h8layout, 9, 5) glayout2.addLayout(h10layout, 11, 3, 1, 3) CustomizeDataFrame = QGroupBox() CustomizeDataFrame.setTitle('Customize Data') CustomizeDataFrame.setLayout(glayout2) #Left Column v2layout.addLayout(h4layout) v2layout.addWidget(CustomizeDataFrame) HardwareFrame = QGroupBox() HardwareFrame.setTitle('Hardware') HardwareFrame.setLayout(glayout) #Right Column v3layout.addWidget(HardwareFrame) maxFileOptionsWidth = 600 self.FileOutput = QLineEdit() self.FileOutput.setMaximumWidth(maxFileOptionsWidth) self.FileOutput.setMaximumHeight(22) self.FileOutput.setToolTip('File path to output file') self.FileOutput.setReadOnly(True) fileUnique = 1 while(os.path.exists("Test-" + str(fileUnique) + '.xlsx')): fileUnique += 1; self.fileUniqueStr = str(os.getcwd()) + '\Test-' + str(fileUnique) + '.xlsx' self.FileOutput.setText(self.fileUniqueStr) self.SaveAs = QPushButton() self.SaveAs.setText('Set Save Filename') self.SaveAs.setMaximumWidth(100) self.SaveAs.setToolTip('Chose where to automatically save recorded data') self.SaveAs.clicked.connect(partial(self.SaveExcelAs, self.fileUniqueStr)) #Left Side of the screen h2layout.addLayout(v2layout) h2layout.addLayout(v3layout) h2layout.addStretch(1) #File Options Widgets h6layout.addWidget(QLabel('Output:')) h6layout.addWidget(self.FileOutput) h6layout.addWidget(self.SaveAs) FileOptionsFrame = QGroupBox() FileOptionsFrame.setTitle('File Options') FileOptionsFrame.setLayout(h6layout) FileOptionsFrame.setMaximumWidth(maxFileOptionsWidth) #Left Size Vertical Layout vlayout.addWidget(QHLine()) vlayout.addLayout(h2layout) vlayout.addWidget(FileOptionsFrame) vlayout.addStretch(1) vlayout.addWidget(self.ButtonStart) #Main Layout hlayout.addLayout(vlayout) hlayout.addWidget(self.Log) #Set the Tab Layout self.tab1.setLayout(hlayout) def update_plot_data(self): if(len(self.x) == 0): self.x = [0] self.xAll = [] self.y = [] self.yAll = [] elif(len(self.x) < self.plotFixedWidth.value()): self.x.append(self.x[-1] + 1) else: self.x = self.x[1:] self.x.append(self.x[-1] + 1) self.y = self.y[1:] self.xAll.append(self.x[-1]) self.y.append(self.DAQ.Read(str(self.COMDis.currentText())) * self.dataOutputMultiplier) self.yAll.append(self.y[-1]) if(self.plotWidth.currentIndex() != 0): self.xLive = self.xAll[-(self.plotFixedWidth.value()):] self.yLive = self.yAll[-(self.plotFixedWidth.value()):] else: self.xLive, self.yLive = self.downsample(0, len(self.xAll)) self.data_line.setData(self.xLive, self.yLive) def tab3UI(self): #Live Data Plot vlayout = QVBoxLayout() hlayout = QHBoxLayout() self.plot = pg.PlotWidget() self.plot.setToolTip('Right click plot for options') self.plot.setBackground('w') self.plot.setTitle("Live Data Plot") self.plot.showGrid(x=True, y=True) self.plot.setLabel('bottom', 'Sample (N)', color = 'gray', size = 40) self.pen = pg.mkPen(color=(59,187,228), width=2) self.penGray = pg.mkPen(color=(180,180,180), width=2) self.x = []#list(range(100)) self.y = []#[randint(0,100) for _ in range(100)] self.xAll = self.x self.yAll = self.y self.testTimer = QtCore.QTimer() self.testTimer.timeout.connect(self.ToggleStartStop) self.timer = QtCore.QTimer() self.timer.setInterval(50) self.timer.timeout.connect(self.update_plot_data) #self.timer.start() self.plotWidth = QComboBox() self.plotWidth.setMaximumWidth(80) self.plotWidth.addItem('All') self.plotWidth.addItem('Fixed Width') self.plotWidth.setToolTip('Sets the x window for streaming data') self.plotWidth.currentIndexChanged.connect(self.plotUpdateData) self.plotWidth.setDisabled(True) self.plotFixedWidth = QSpinBox() self.plotFixedWidth.hide() self.plotFixedWidth.setMinimum(10) self.plotFixedWidth.setMaximum(100000) self.plotFixedWidth.setValue(100) self.plotFixedWidth.valueChanged.connect(self.plotUpdateData) self.plotFixedWidth.setDisabled(True) self.plotZero = QPushButton() self.plotZero.setText('Zero') self.plotZero.setToolTip('Zeroes the sensor data') self.plotZero.clicked.connect(self.zeroSensor) self.plotZero.setDisabled(True) self.plotStop = QPushButton() self.plotStop.setText('Start') self.plotStop.setToolTip('Stops the data Stream') self.plotStop.clicked.connect(self.ToggleStartStop) vlayout.addWidget(self.plotWidth) vlayout.addWidget(self.plotFixedWidth) vlayout.addWidget(self.plotZero) vlayout.addWidget(QLabel('(Right click plot for\nadditional options)')) vlayout.addStretch(1) vlayout.addWidget(self.plotStop) hlayout.addLayout(vlayout) hlayout.addWidget(self.plot) self.tab3.setLayout(hlayout) def tab4UI(self): hlayout = QHBoxLayout() self.tab4.setLayout(hlayout) #============================================================================== # Input Parameters: none # Output Returns: none (Tab 2 Layout) # # Description: This funcition defines the layout for the second Tab and associated # widgets. #============================================================================== def tab2UI(self): #Define all the different Layouts vlayout = QVBoxLayout() #Main Layout v2layout = QVBoxLayout() hlayout = QHBoxLayout() h2layout = QHBoxLayout() glayout = QGridLayout() h3layout = QHBoxLayout() #Creat a font style to bold the headers headerFont = QFont() headerFont.setBold(True) #Loaded File Location self.pFileName = QLineEdit() self.pFileName.setMaximumHeight(22) self.pFileName.setReadOnly(True) self.pFileName.setToolTip('Location of data to plot') #Import Button self.pImport = QPushButton() self.pImport.setMaximumWidth(80) self.pImport.setText('Import') self.pImport.setToolTip('Import Data to plot') self.pImport.clicked.connect(self.OpenExcel) #Plot Button self.pPlot = QPushButton() self.pPlot.setMaximumWidth(80) self.pPlot.setText('Plot') self.pPlot.setToolTip('Plot the data') self.pPlot.setDisabled(True) self.pPlot.clicked.connect(self.PlotData) #Individual Radio Button self.pAllLines = QRadioButton('Individual') self.pAllLines.setChecked(True) self.pAllLines.setToolTip('Gives each line a unique color') #By Cup Radio Button self.pCupLines = QRadioButton('By Cup') self.pCupLines.setToolTip('Highlights the data by cup') #Hightlighting Header highlight = QLabel('Highlighting') highlight.setFont(headerFont) #Highlighting Data glayout.addWidget(self.pAllLines, 1, 3) glayout.addWidget(self.pCupLines, 1, 5) HighlightingFrame = QGroupBox() HighlightingFrame.setTitle('Highlighting') HighlightingFrame.setLayout(glayout) #Left Column v2layout.addWidget(HighlightingFrame) v2layout.addStretch(1) #Main Horizontal Layout for Columns h3layout.addLayout(v2layout) h3layout.addStretch(1) #Import Layout hlayout.addWidget(QLabel('Import:')) hlayout.addWidget(self.pFileName) hlayout.addWidget(self.pImport) #Plot button h2layout.addStretch(1) h2layout.addWidget(self.pPlot) #Main Layout vlayout.addWidget(QHLine()) vlayout.addLayout(hlayout) vlayout.addLayout(h3layout) vlayout.addStretch(1) vlayout.addLayout(h2layout) #Set the Layout for Tab2 self.tab2.setLayout(vlayout) #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function updates the DataMultiplier LineEdit based on the # selected DataOutput ComboBox #============================================================================== def updateMult(self): self.DataMultiplier.setText(self.OutputOptions[str(self.DataOutput.currentText())]) key = self.DataOutput.currentText() self.DataMultiplier.setDisabled(True) if(key == 'Temp 700'): self.DataMultiplier.setToolTip('A custom equation is required to convert to temperature<br><b>700 Ohm Thermistors</b>') elif(key == 'Temp 800'): self.DataMultiplier.setToolTip('A custom equation is required to convert to temperature<br><b>800 Ohm Thermistors</b>') elif(key == 'Custom'): self.DataMultiplier.setToolTip('Enter in a Custom Multiplier to convert the A to D signal') self.DataMultiplier.setDisabled(False) else: self.DataMultiplier.setToolTip('Multiplier to convert the A to D signal') #============================================================================== # Input Parameters: none # Output Returns: none (HTML Plot of Selected Data) # # Description: This function imports XLSX information, takes a couple user inputs # and then plots the file to an HTML file using Bokeh. #============================================================================== def PlotData(self): print ('Plotting Data') numlines = len(self.dfData.columns) #Number of Columns if(self.pAllLines.isChecked()): #Individual Radio Button Selected #Palet containing 64 unique colors mypalette = ['black','dimgray','lightgrey','rosybrown','brown','maroon','red','salmon','sienna','chocolate','saddlebrown','sandybrown','orange','darkgoldenrod','gold','olivedrab','yellowgreen','darkolivegreen','chartreuse','darkseagreen','limegreen','darkgreen','green','lime','springgreen','mediumspringgreen','mediumaquamarine','aquamarine','turquoise','mediumturquoise','lightseagreen','darkcyan','aqua','cadetblue','deepskyblue','skyblue','lightskyblue','steelblue','royalblue','midnightblue','blue','navy','slateblue','mediumslateblue','darkorchid','mediumpurple','darkviolet','indigo','darkviolet','darkorchid','purple','darkmagenta','orchid','magenta','deeppink','crimson','pink','mediumvioletred','palevioletred','darkorange','peru','tan','coral','dodgerblue'] #Legend separating the data by the 'Line' on the MUX #Note: Using a unique legend for each column makes the legend too big legends = ['Line 1','Line 2','Line 3','Line 4','Line 5','Line 6','Line 7','Line 8'] * 8 elif(self.pCupLines.isChecked()): #By Cup Radio Button Selected Palette1 = ['firebrick'] * 16 #Set Cup 1 to a single color Palette2 = ['seagreen'] * 16 #Set Cup 2 to a single color Palette3 = ['navy'] * 16 #Set Cup 3 to a single color Palette4 = ['darkmagenta'] * 16 #Set Cup 4 to a single color mypalette = Palette1 + Palette2 + Palette3 + Palette4 #Creat a Master List of Colors #Give each Cup a legend legends = ['Cup1'] * 16 + ['Cup2'] * 16 + ['Cup3'] * 16 + ['Cup4'] * 16 #Bokeh Plot Object p = figure(toolbar_location = 'above', plot_width = 1650, plot_height = 800, tools = ['box_zoom', 'pan', 'wheel_zoom', 'reset', 'save']) xs=[self.dfData.index.values]*numlines #List of a List of X Index values ys = [self.dfData[name].values for name in self.dfData] #List of a List of Y Values #Loop through all the data and add a line plot for each Channel for (colr, leg, x, y) in zip(mypalette, legends, xs, ys): p.line(x, y, color = colr, legend = leg) #Bokeh Line with given color and legend p.legend.click_policy = 'hide' #Allows the legend to be clicked to hide/show data show(p)#Show the resulting Plot print ('Data Plotted!') #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function is connected to a 'textChanged' event from the rate # and will auto update the firmware rate #============================================================================== def SetRate(self): rate = self.DataRate.text() if float(rate) > self.rateMax: rate = str(self.rateMax) self.DataRate.setText(rate) self.logMsg('Warning! - Maximum rate is ' + rate + 'Hz', True, 'orange') newRate = int(1000 / float(self.DataRate.text())) if(newRate != self.oldRate): self.timer.setInterval(newRate) self.logMsg('<b>Sample Time: ' + str(newRate) + 'ms</b>', False, 'blue') self.oldRate = newRate #============================================================================== # Input Parameters: Argument (string) *Automatically passed in I guess # Output Returns: self.DataMultiplier.setText (string) # # Description: This function is connected to a 'textChanged' event from either # a QTextEdit or QLineEdit object. Whenever a text value is inputed in the text # field by the user, this function will look at the typed character and remove # it from the resulting string if that character is not an integer, (0-9) a # period (.) or a minus (-) symbol. #============================================================================== def OnlyAllowInt(self, arg): #Setup a list of valid characters valid = ['-','.','0','1','2','3','4','5','6','7','8','9'] #Loop through the inputed argument for c in range(0, len(arg)): #Look at the specific character of the string and check to see if it is valid if arg[c] not in valid: #If there is an invalid character, replace the character with an empty string ('') if len(arg)>0: arg=arg.replace(arg[c],'') #Set the text in DataMultiplier to the 'cleaned' argument self.DataMultiplier.blockSignals(True) self.DataMultiplier.setText(arg) self.DataMultiplier.blockSignals(False) #============================================================================== # Input Parameters: Argument (string) *Automatically passed in I guess # Output Returns: self.DataTime.setText (string) # # Description: This function is connected to a 'textChanged' event from either # a QTextEdit or QLineEdit object. Whenever a text value is inputed in the text # field by the user, this function will look at the typed character and remove # it from the resulting string if that character is not an integer, (0-9). #============================================================================== def OnlyAllowInt2(self, arg): #Setup a list of valid characters valid = ['0','1','2','3','4','5','6','7','8','9'] #Loop through the inputed argument for c in range(0, len(arg)): #Look at the specific character of the string and check to see if it is valid if arg[c] not in valid: #If there is an invalid character, replace the character with an empty string ('') if len(arg)>0: arg=arg.replace(arg[c],'') if(arg == '0'): arg = '-' #Set the text in DataTime to the 'cleaned' argument self.DataTime.blockSignals(True) self.DataTime.setText(arg) self.DataTime.blockSignals(False) #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function opens the window file dialog to save a file #============================================================================== def SaveExcelAs(self, fnameIn): if (os.path.exists(fnameIn)): #file already exists x = fnameIn.split('-') y = len(x) m = 1 fnameIn = x[0] if (y > 2): i = 2 while (y > i): fnameIn = fnameIn + '-' + x[i-1] i += 1 if (len(x[y-1]) > 0): z = x[y-1].split('.xlsx') if (len(z[0]) > 0): m = int(z[0]) + 1 fnameIn = fnameIn + '-' + str(m) + '.xlsx' fname = QFileDialog.getSaveFileName(self, 'Save file', fnameIn, "Excel files (*.xlsx)") if fname != '': #print('Save File As: ' + str(fname)) self.FileOutput.setText(fname[0]) self.fileUniqueStr = fname[0] #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function opens the window file dialog to open a file and then # Reads the Sheet1 of the file into a pandas Data Frame #============================================================================== def OpenExcel(self): fname = QFileDialog.getOpenFileName(self, 'Save file', 'c:\\',"Excel files (*.xlsx)") if fname != '': self.pFileName.setText(fname) print ('Opening File') try: self.dfData = pd.read_excel(open(fname, 'rb'), sheet_name = 'Sheet1') print ('Data Extracted from File!') numlines = len(self.dfData.columns) #Number of Columns if(numlines != 64): self.pPlot.setDisabled(True) print (')ERROR - Plotter can only plot data with 64 Columns!') else: self.pPlot.setDisabled(False) except: print ('ERROR - Could not Extract data from file!') print ('Ensure desired data is on sheet 1 of the file') self.dfData = None self.pPlot.setDisabled(True) #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function Refreshes the COM Port List #============================================================================== def RefreshCOMs(self): self.logMsg('Refresh COM Ports<br>', False, 'black') self.SearchCOMs() #============================================================================== # Input Parameters: none # Output Returns: none # # Description: updates the plot fixed width if in fixed width mode #============================================================================== def plotUpdateData(self): if(self.plotWidth.currentIndex() != 0): self.plotFixedWidth.show() self.x = self.xAll[(len(self.xAll) - self.plotFixedWidth.value()):] self.y = self.yAll[(len(self.yAll) - self.plotFixedWidth.value()):] else: self.plotFixedWidth.hide() #============================================================================== # Input Parameters: none # Output Returns: none # # Description: downsamples the data for the plot #============================================================================== def downsample(self, xstart, xend): max_points = self.plotFixedWidth.value() if(len(self.xAll) > max_points): origXData = np.asarray(self.xAll) origYData = np.asarray(self.yAll) mask = (origXData > xstart) & (origXData < xend) mask = np.convolve([1,1], mask, mode='same').astype(bool) ratio = max(np.sum(mask) // max_points, 1) xdata = origXData[mask] ydata = origYData[mask] xdata = xdata[::ratio] ydata = ydata[::ratio] else: xdata = self.xAll ydata = self.yAll return xdata, ydata def convertOutput(self): if(self.outputType == 'Pressure'): if(self.DataOutput.currentText() == 'PSI'): self.dataOutputMultiplier = 1.0 elif(self.DataOutput.currentText() == 'HPA'): self.dataOutputMultiplier = 6894.7572932 elif(self.DataOutput.currentText() == 'BAR'): self.dataOutputMultiplier = 0.068947572932 elif(self.DataOutput.currentText() == 'MBAR'): self.dataOutputMultiplier = 68.947572932 elif(self.DataOutput.currentText() == 'KPA'): self.dataOutputMultiplier = 6.8947572932 elif(self.DataOutput.currentText() == 'CMH2O'): self.dataOutputMultiplier = 70.3069578296 elif(self.DataOutput.currentText() == 'INH2O'): self.dataOutputMultiplier = 27.6799048425 elif(self.DataOutput.currentText() == 'MMHG'): self.dataOutputMultiplier = 51.71492 self.plot.setLabel('left', 'Pressure (' + str(self.DataOutput.currentText()) + ')', color = 'gray', size = 40) self.DataMultiplier.setText("{:.2f}".format(self.dataOutputMultiplier)) #============================================================================== # Input Parameters: none # Output Returns: none # # Description: This function Starts and Stops the Data Acqusition. This function # will either stop after all the Samples have been taken or when the user presses # the 'Stop' button. #============================================================================== def ToggleStartStop(self): self.Start = not(self.Start) if(not(self.Start)): print ('Stop Test') self.timer.stop() self.testTimer.stop() self.DAQ.Abort = True self.DAQ.CloseCOM(str(self.COMDis.currentText())) self.plot.clear() self.data_line = self.plot.plot(self.xLive, self.yLive, pen=self.penGray) self.setCurrentIndex(0) self.tab1.setDisabled(False) self.plotStop.setText('Start') self.plotZero.setDisabled(True) self.plotWidth.setDisabled(True) self.plotFixedWidth.setDisabled(True) self.RefreshCOM.setDisabled(False) self.ButtonStart.setToolTip('Start Recording Data') self.logMsg('Saving Data...', True, 'black') if(os.path.exists(self.fileUniqueStr)): reply = QMessageBox.question(self, 'Overwrite File?', 'File already exists,\ndo you want to overwrite?', buttons=QMessageBox.No|QMessageBox.Yes, defaultButton=QMessageBox.No) if reply == QMessageBox.No: self.SaveExcelAs(self.fileUniqueStr) if(self.SaveData(self.fileUniqueStr)): self.logMsg(self.FileOutput.text(), False, 'black') self.logMsg('...Data Saved!', True, '#00aa00') else: self.logMsg('...Data Could Not be Saved', True, 'red') self.logMsg('Check Recovery Files', True, 'red') else: print ('Starting Test') self.plotStop.setText('Stop') self.plotZero.setDisabled(False) self.plotWidth.setDisabled(False) self.plotFixedWidth.setDisabled(False) self.RefreshCOM.setDisabled(True) self.tab1.setDisabled(True) self.ButtonStart.setToolTip('Stop Recording Data') self.setCurrentIndex(1) self.plot.clear() self.x=[] self.y=[] self.data_line = self.plot.plot(self.x, self.y, pen=self.pen) if(self.DataTime.displayText() != '-'): testTime = int(float(self.DataTime.displayText())*1000) self.testTimer.start(testTime) print('Starting test for: '+str(testTime)+' mSec') self.timer.start() COMPort = str(self.COMDis.currentText()) print (COMPort) # if(COMPort != 'NA'): # if(self.FirmDis.text() != 'NA'): # self.logMsg('--DAQ Settings--<br><br>', True, '#900090') # self.logMsg('Hardware Detected: ' + COMPort, False, 'black') # #Set the ADC Multiplier # self.DAQ.SetTemp('NA') #Set the Temp variable in the MaxtecDAQ Class to NA # if(self.DataMultiplier.text() == ''): #Make sure there is a multiplier value # self.DataMultiplier.setText('1') #Default to 1 # if(self.DataOutput.currentText() == 'Temp 700'): #Check if the output is a Temperature # self.DAQ.SetTemp('Temp700') #Set the Temp variable in the MaxtecDAQ Class to convert the final results # elif(self.DataOutput.currentText() == 'Temp 800'): #Check if the output is a Temperature # self.DAQ.SetTemp('Temp800') #Set the Temp variable in the MaxtecDAQ Class to convert the final results # self.DAQ.ADCMult = float(self.DataMultiplier.text()) # print ('ADCMult set to: ' + str(self.DataMultiplier.text())) # self.logMsg('Multiplier: ' + str(self.DAQ.ADCMult), False, 'black') # #Set the Recording Time # Time = self.DataTime.text() # self.logMsg('Record Time: ' + Time + ' Sec', False, 'black') # #Set the Sample Rate # SampRate = float(self.DataRate.text()) # self.logMsg('Sample Rate: ' + str(SampRate) + ' Samp/Sec', False, 'black') # #Set the number of Samples # SampNum = int(int(Time) * SampRate) # self.logMsg('Number of Samples: ' + str(SampNum) + ' Samp', False, 'black') # Prefix = self.DataPrefix.text() # self.logMsg('Header Names: ' + Prefix + 'xx', False, 'black') # self.logMsg('Channels: ' + str(self.HardChannels.text()), False, 'black') # #Get Firmware Version # FirmVer = self.DAQ.getFirmVer(COMPort) # if(FirmVer != 'NA'): # self.logMsg('Firmware: ' + str(FirmVer), False, 'black') # self.FirmDis.setText(FirmVer) # else: # self.logMsg('ERROR! - Could not get Firmware from Teensy', True, 'red') # self.FirmDis.setText('NA') # List = [] # for i in range(0, int(self.HardChannels.text())): # if(i < 9): # List.append(str(Prefix + '0' + str(i + 1))) # else: # List.append(str(Prefix + str(i + 1))) # print (List) # #Start Recording Data # self.logMsg('Data Recording Started...', True, '#0000ff') # self.DAQ.ReadStart(List, SampNum, COMPort, SampRate) # self.logMsg('...Data Recording Stopped', True, '#0000ff') # self.logMsg('Saving Data...', True, '#00aa00') # if(self.DAQ.SaveData(self.fileUniqueStr)): # self.logMsg(self.FileOutput.text(), False, 'black') # self.logMsg('...Data Saved!', True, '#00aa00') # else: # self.logMsg('...Data Could Not be Saved', True, 'red') # self.logMsg('Check Recovery Files', True, 'red') # else: # self.DAQ.ReadStreamStart(COMPort) # with open(self.FileOutput.text(), "w") as text_files: # text_files.write(self.Log.toPlainText()) # self.logMsg('Data Saved', True, '#00aa00') # #Reset the start latch # self.Start = not(self.Start) # #Reset the start button # self.ButtonStart.setText('Start') # self.ButtonStart.setToolTip('Start Recording Data') # else: # COM Port not found # self.logMsg('ERROR! - Teensy COM Port NOT Found', True, 'red') def zeroSensor(self): self.logMsg('Zeroing Sensor Output', False, 'blue') if(self.DAQ.zero()): self.logMsg('Zero Set', False, 'green') else: self.logMsg('Zero NOT Set', False, 'red') def SaveData(self, fname): print ("Saving to Excel") timeIndex = [x/float(self.DataRate.displayText()) for x in self.xAll] pressureHeading = 'Pressure ('+self.DataOutput.currentText()+')' data = {'Time (sec)':timeIndex, pressureHeading:self.yAll} self.df = pd.DataFrame(data=data) try: self.df.to_excel(str(fname)) print ("File Saved!") return True except: print ("ERROR - Could not save Excel File!") RecoveryPath = str(os.getcwd()) + '\Recovery-' + str(datetime.datetime.now().strftime('%H%M%S') + '.xlsx') self.df.to_excel(RecoveryPath) print ("File Recovery avaliable at: " + RecoveryPath) return False def closeEvent(self, event): if self.Start: quit_msg = 'Are you sure you want to exit the program?' reply = QMessageBox.question(self, 'Message', quit_msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: self.ToggleStartStop() event.accept() else: event.ignore() else: event.accept()
class OptionsDialog(QDialog): def __init__(self, setting: Settings, have_dutils, parent=None): super(OptionsDialog, self).__init__(parent) self.settings = setting self.enabled_video = True # temporary toggle to disable video features as they do not exist self.enabled_logging = True self.enabled_keybindings = True self.enabled_dutils = have_dutils self.setWindowTitle("Tcam-Capture Options") self.layout = QVBoxLayout(self) self.setLayout(self.layout) self.tabs = QTabWidget() self.general_widget = QWidget() self.keybindings_widget = QWidget() self.logging_widget = QWidget() self.saving_widget = QWidget() self._setup_general_ui() self.tabs.addTab(self.general_widget, "General") if self.enabled_keybindings: self._setup_keybindings_ui() self.tabs.addTab(self.keybindings_widget, "Keybindings") self._setup_saving_ui() self.tabs.addTab(self.saving_widget, "Image/Video") self.layout.addWidget(self.tabs) # OK and Cancel buttons self.buttons = QDialogButtonBox( QDialogButtonBox.Reset | QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.layout.addWidget(self.buttons) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.buttons.clicked.connect(self.clicked) def _setup_general_ui(self): """ Create everything related to the general tab """ layout = QFormLayout() layout.setSpacing(20) layout.setVerticalSpacing(20) self.device_dialog_checkbox = QCheckBox(self) device_dialog_label = QLabel("Open device dialog on start:") layout.addRow(device_dialog_label, self.device_dialog_checkbox) self.reopen_device_checkbox = QCheckBox(self) reopen_device_label = QLabel("Reopen device on start(ignores device dialog):", self) layout.addRow(reopen_device_label, self.reopen_device_checkbox) self.use_dutils_checkbox = QCheckBox(self) self.use_dutils_label = QLabel("Use tiscamera dutils, if present:", self) layout.addRow(self.use_dutils_label, self.use_dutils_checkbox) if not self.enabled_dutils: self.use_dutils_label.setToolTip("Enabled when tiscamera-dutils are installed") self.use_dutils_label.setEnabled(False) self.use_dutils_checkbox.setToolTip("Enabled when tiscamera-dutils are installed") self.use_dutils_checkbox.setEnabled(False) self.general_widget.setLayout(layout) def _setup_saving_ui(self): """ Create everything related to the image/video saving tab """ encoder_dict = Encoder.get_encoder_dict() form_layout = QFormLayout() layout = QVBoxLayout() layout.addLayout(form_layout) location_layout = QHBoxLayout() location_label = QLabel("Where to save images/videos:", self) self.location_edit = QLineEdit(self) location_dialog_button = QPushButton("...", self) location_dialog_button.clicked.connect(self.open_file_dialog) location_layout.addWidget(self.location_edit) location_layout.addWidget(location_dialog_button) # maintain descriptions as own labels # pyqt seems to loose the descriptions somewhere # when simple strings are used or the qlabel does not have self as owner form_layout.addRow(location_label, location_layout) self.image_type_combobox = QComboBox(self) for key, value in encoder_dict.items(): if value.encoder_type == Encoder.MediaType.image: self.image_type_combobox.addItem(key) image_type_label = QLabel("Save images as:") self.image_type_combobox.currentIndexChanged['QString'].connect(self.image_name_suffix_changed) form_layout.addRow(image_type_label, self.image_type_combobox) if self.enabled_video: self.video_type_combobox = QComboBox(self) for key, value in encoder_dict.items(): if value.encoder_type == Encoder.MediaType.video: self.video_type_combobox.addItem(key) self.video_type_combobox.currentIndexChanged['QString'].connect(self.video_name_suffix_changed) video_type_label = QLabel("Save videos as:", self) form_layout.addRow(video_type_label, self.video_type_combobox) image_name_groupbox = QGroupBox("Image File Names") groupbox_layout = QFormLayout() image_name_groupbox.setLayout(groupbox_layout) self.image_name_preview = QLabel("<USER-PREFIX>-<SERIAL>-<FORMAT>-<TIMESTAMP>-<COUNTER>.png") self.image_name_preview_description = QLabel("Images will be named like:") groupbox_layout.addRow(self.image_name_preview_description, self.image_name_preview) self.image_name_prefix = QLineEdit() self.image_name_prefix.textChanged.connect(self.image_name_prefix_changed) self.image_name_prefix.setMaxLength(100) self.image_name_prefix_description = QLabel("User Prefix:", self) groupbox_layout.addRow(self.image_name_prefix_description, self.image_name_prefix) self.image_name_serial = QCheckBox(self) self.image_name_serial.toggled.connect(self.image_name_properties_toggled) self.image_name_serial_description = QLabel("Include Serial:") groupbox_layout.addRow(self.image_name_serial_description, self.image_name_serial) self.image_name_format = QCheckBox(self) self.image_name_format.toggled.connect(self.image_name_properties_toggled) self.image_name_format_description = QLabel("Include Format:") groupbox_layout.addRow(self.image_name_format_description, self.image_name_format) self.image_name_counter = QCheckBox(self) self.image_name_counter.toggled.connect(self.image_name_properties_toggled) self.image_name_counter_description = QLabel("Include Counter:") groupbox_layout.addRow(self.image_name_counter_description, self.image_name_counter) self.image_name_counter_box = QSpinBox(self) self.image_name_counter_box.setRange(1, 10) self.image_name_counter_box.valueChanged.connect(self.image_name_counter_changed) self.image_name_counter_box_description = QLabel("Counter Size:") groupbox_layout.addRow(self.image_name_counter_box_description, self.image_name_counter_box) self.image_name_counter.toggled.connect(self.toggle_image_counter_box_availability) self.image_name_counter.toggled.connect(self.image_name_properties_toggled) self.image_name_timestamp = QCheckBox(self) self.image_name_timestamp.toggled.connect(self.image_name_properties_toggled) self.image_name_timestamp_description = QLabel("Include Timestamp:") groupbox_layout.addRow(self.image_name_timestamp_description, self.image_name_timestamp) layout.addWidget(image_name_groupbox) video_groupbox = QGroupBox("Video File Names") video_layout = QFormLayout() video_groupbox.setLayout(video_layout) self.video_name_preview = QLabel("<USER-PREFIX>-<SERIAL>-<FORMAT>-<TIMESTAMP>-<COUNTER>.png") self.video_name_preview_description = QLabel("Videos will be named like:") video_layout.addRow(self.video_name_preview_description, self.video_name_preview) self.video_name_prefix = QLineEdit() self.video_name_prefix.textChanged.connect(self.video_name_prefix_changed) self.video_name_prefix.setMaxLength(100) self.video_name_prefix_description = QLabel("User Prefix:", self) video_layout.addRow(self.video_name_prefix_description, self.video_name_prefix) self.video_name_serial = QCheckBox(self) self.video_name_serial.toggled.connect(self.video_name_properties_toggled) self.video_name_serial_description = QLabel("Include Serial:") video_layout.addRow(self.video_name_serial_description, self.video_name_serial) self.video_name_format = QCheckBox(self) self.video_name_format.toggled.connect(self.video_name_properties_toggled) self.video_name_format_description = QLabel("Include Format:") video_layout.addRow(self.video_name_format_description, self.video_name_format) self.video_name_counter = QCheckBox(self) self.video_name_counter.toggled.connect(self.video_name_properties_toggled) self.video_name_counter_description = QLabel("Include Counter:") video_layout.addRow(self.video_name_counter_description, self.video_name_counter) self.video_name_counter_box = QSpinBox(self) self.video_name_counter_box.setRange(1, 10) self.video_name_counter_box.valueChanged.connect(self.video_name_counter_changed) self.video_name_counter_box_description = QLabel("Counter Size:") video_layout.addRow(self.video_name_counter_box_description, self.video_name_counter_box) self.video_name_counter.toggled.connect(self.toggle_video_counter_box_availability) self.video_name_counter.toggled.connect(self.video_name_properties_toggled) self.video_name_timestamp = QCheckBox(self) self.video_name_timestamp.toggled.connect(self.video_name_properties_toggled) self.video_name_timestamp_description = QLabel("Include Timestamp:") video_layout.addRow(self.video_name_timestamp_description, self.video_name_timestamp) layout.addWidget(video_groupbox) self.saving_widget.setLayout(layout) def image_name_prefix_changed(self, name: str): """""" self.settings.image_name.user_prefix = self.image_name_prefix.text() self.update_image_name_preview() def image_name_suffix_changed(self, suffix: str): """""" self.update_image_name_preview() def image_name_counter_changed(self, name: str): """""" self.settings.image_name.counter_size = self.image_name_counter_box.value() self.update_image_name_preview() def image_name_properties_toggled(self): """""" self.settings.image_name.include_timestamp = self.image_name_timestamp.isChecked() self.settings.image_name.include_counter = self.image_name_counter.isChecked() self.settings.image_name.include_format = self.image_name_format.isChecked() self.settings.image_name.include_serial = self.image_name_serial.isChecked() self.update_image_name_preview() def update_image_name_preview(self): preview_string = "" if self.settings.image_name.user_prefix != "": max_prefix_length = 15 prefix = (self.settings.image_name.user_prefix[:max_prefix_length] + '..') if len(self.settings.image_name.user_prefix) > max_prefix_length else self.settings.image_name.user_prefix preview_string += prefix if self.settings.image_name.include_serial: if preview_string != "": preview_string += "-" preview_string += "00001234" if self.settings.image_name.include_format: if preview_string != "": preview_string += "-" preview_string += "gbrg_1920x1080_15_1" if self.settings.image_name.include_timestamp: if preview_string != "": preview_string += "-" preview_string += "19701230T125503" if self.settings.image_name.include_counter: if preview_string != "": preview_string += "-" preview_string += '{message:0>{fill}}'.format(message=1, fill=self.settings.image_name.counter_size) if preview_string == "": preview_string = "image" preview_string += "." + self.image_type_combobox.currentText() self.image_name_preview.setText(preview_string) def video_name_prefix_changed(self, name: str): """""" self.settings.video_name.user_prefix = self.video_name_prefix.text() self.update_video_name_preview() def video_name_suffix_changed(self, suffix: str): """""" self.update_video_name_preview() def video_name_counter_changed(self, name: str): """""" self.settings.video_name.counter_size = self.video_name_counter_box.value() self.update_video_name_preview() def video_name_properties_toggled(self): """""" self.settings.video_name.include_timestamp = self.video_name_timestamp.isChecked() self.settings.video_name.include_counter = self.video_name_counter.isChecked() self.settings.video_name.include_format = self.video_name_format.isChecked() self.settings.video_name.include_serial = self.video_name_serial.isChecked() self.update_video_name_preview() def update_video_name_preview(self): preview_string = "" if self.settings.video_name.user_prefix != "": # This is a convenience change to the displayed string. # We only display an amount of max_prefix_length # chars to save screen space max_prefix_length = 15 prefix = (self.settings.video_name.user_prefix[:max_prefix_length] + '..') if len(self.settings.video_name.user_prefix) > max_prefix_length else self.settings.video_name.user_prefix preview_string += prefix if self.settings.video_name.include_serial: if preview_string != "": preview_string += "-" preview_string += "00001234" if self.settings.video_name.include_format: if preview_string != "": preview_string += "-" preview_string += "gbrg_1920x1080_15_1" if self.settings.video_name.include_timestamp: if preview_string != "": preview_string += "-" preview_string += "19701230T125503" if self.settings.video_name.include_counter: if preview_string != "": preview_string += "-" preview_string += '{message:0>{fill}}'.format(message=1, fill=self.settings.video_name.counter_size) if preview_string == "": preview_string = "video" preview_string += "." + self.video_type_combobox.currentText() self.video_name_preview.setText(preview_string) def toggle_image_counter_box_availability(self): """""" if self.image_name_counter.isChecked(): self.image_name_counter_box.setEnabled(True) else: self.image_name_counter_box.setEnabled(False) def toggle_video_counter_box_availability(self): """""" if self.video_name_counter.isChecked(): self.video_name_counter_box.setEnabled(True) else: self.video_name_counter_box.setEnabled(False) def _setup_keybindings_ui(self): """ Create everything related to the keybindings tab """ layout = QFormLayout() self.keybinding_fullscreen_label = QLabel("Toggle Fullscreen:") self.keybinding_fullscreen = QKeySequenceEdit() layout.addRow(self.keybinding_fullscreen_label, self.keybinding_fullscreen) self.keybinding_save_image_label = QLabel("Save image:") self.keybinding_save_image = QKeySequenceEdit(QKeySequence(self.settings.keybinding_save_image)) layout.addRow(self.keybinding_save_image_label, self.keybinding_save_image) self.keybinding_trigger_image_label = QLabel("Trigger images via softwaretrigger:") self.keybinding_trigger_image = QKeySequenceEdit(QKeySequence(self.settings.keybinding_trigger_image)) layout.addRow(self.keybinding_trigger_image_label, self.keybinding_trigger_image) self.keybinding_open_dialog_label = QLabel("Open device dialog:") self.keybinding_open_dialog = QKeySequenceEdit(QKeySequence(self.settings.keybinding_open_dialog)) layout.addRow(self.keybinding_open_dialog_label, self.keybinding_open_dialog) self.keybindings_widget.setLayout(layout) def set_settings(self, settings: Settings): self.location_edit.setText(settings.get_save_location()) self.image_type_combobox.setCurrentText(settings.get_image_type()) if self.enabled_video: self.video_type_combobox.setCurrentText(settings.get_video_type()) self.device_dialog_checkbox.setChecked(settings.show_device_dialog_on_startup) self.reopen_device_checkbox.setChecked(settings.reopen_device_on_startup) self.use_dutils_checkbox.setChecked(settings.use_dutils) # # keybindings # if self.enabled_keybindings: self.keybinding_fullscreen.setKeySequence(QKeySequence(self.settings.keybinding_fullscreen)) self.keybinding_save_image.setKeySequence(QKeySequence(self.settings.keybinding_save_image)) self.keybinding_trigger_image.setKeySequence(QKeySequence(self.settings.keybinding_trigger_image)) self.keybinding_open_dialog.setKeySequence(QKeySequence(self.settings.keybinding_open_dialog)) # # image saving # if settings.image_name.include_timestamp: self.image_name_timestamp.blockSignals(True) self.image_name_timestamp.toggle() self.image_name_timestamp.blockSignals(False) if settings.image_name.include_counter: self.image_name_counter.blockSignals(True) self.image_name_counter.toggle() self.image_name_counter.blockSignals(False) self.image_name_counter_box.blockSignals(True) self.image_name_counter_box.setValue(settings.image_name.counter_size) self.image_name_counter_box.blockSignals(False) self.toggle_image_counter_box_availability() if settings.image_name.include_format: self.image_name_format.blockSignals(True) self.image_name_format.toggle() self.image_name_format.blockSignals(False) if settings.image_name.include_serial: self.image_name_serial.blockSignals(True) self.image_name_serial.toggle() self.image_name_serial.blockSignals(False) self.image_name_prefix.blockSignals(True) self.image_name_prefix.setText(settings.image_name.user_prefix) self.image_name_prefix.blockSignals(False) self.update_image_name_preview() # # video saving # if settings.video_name.include_timestamp: self.video_name_timestamp.blockSignals(True) self.video_name_timestamp.toggle() self.video_name_timestamp.blockSignals(False) if settings.video_name.include_counter: self.video_name_counter.blockSignals(True) self.video_name_counter.toggle() self.video_name_counter.blockSignals(False) self.video_name_counter_box.blockSignals(True) self.video_name_counter_box.setValue(settings.video_name.counter_size) self.video_name_counter_box.blockSignals(False) self.toggle_video_counter_box_availability() if settings.video_name.include_format: self.video_name_format.blockSignals(True) self.video_name_format.toggle() self.video_name_format.blockSignals(False) if settings.video_name.include_serial: self.video_name_serial.blockSignals(True) self.video_name_serial.toggle() self.video_name_serial.blockSignals(False) self.video_name_prefix.blockSignals(True) self.video_name_prefix.setText(settings.video_name.user_prefix) self.video_name_prefix.blockSignals(False) self.update_video_name_preview() def save_settings(self): self.settings.save_location = self.location_edit.text() self.settings.image_type = self.image_type_combobox.currentText() if self.enabled_video: self.settings.video_type = self.video_type_combobox.currentText() self.settings.show_device_dialog_on_startup = self.device_dialog_checkbox.isChecked() self.settings.reopen_device_on_startup = self.reopen_device_checkbox.isChecked() self.settings.use_dutils = self.use_dutils_checkbox.isChecked() # # keybindings # if self.enabled_keybindings: self.settings.keybinding_fullscreen = self.keybinding_fullscreen.keySequence().toString() self.settings.keybinding_save_image = self.keybinding_save_image.keySequence().toString() self.settings.keybinding_trigger_image = self.keybinding_trigger_image.keySequence().toString() self.settings.keybinding_open_dialog = self.keybinding_open_dialog.keySequence().toString() # # image saving # self.settings.image_name.include_timestamp = self.image_name_timestamp.isChecked() self.settings.image_name.include_counter = self.image_name_counter.isChecked() if self.image_name_counter.isChecked(): self.settings.image_name.counter_size = self.image_name_counter_box.value() self.settings.image_name.include_format = self.image_name_format.isChecked() self.settings.image_name.include_serial = self.image_name_serial.isChecked() self.settings.image_name.user_prefix = self.image_name_prefix.text() # # video saving # self.settings.video_name.include_timestamp = self.video_name_timestamp.isChecked() self.settings.video_name.include_counter = self.video_name_counter.isChecked() if self.video_name_counter.isChecked(): self.settings.video_name.counter_size = self.video_name_counter_box.value() self.settings.video_name.include_format = self.video_name_format.isChecked() self.settings.video_name.include_serial = self.video_name_serial.isChecked() self.settings.video_name.user_prefix = self.video_name_prefix.text() def open_file_dialog(self): fdia = QFileDialog() fdia.setFileMode(QFileDialog.Directory) fdia.setWindowTitle("Select Directory for saving images and videos") if fdia.exec_(): self.location_edit.setText(fdia.selectedFiles()[0]) def get_location(self): return self.location_edit.text() def get_image_format(self): return self.image_type_combobox.currentText() def get_video_format(self): return self.video_type_combobox.currentText() def clicked(self, button): if self.buttons.buttonRole(button) == QDialogButtonBox.ResetRole: self.reset() def reset(self): """""" log.info("reset called") self.settings.reset() self.set_settings(self.settings) @staticmethod def get_options(settings, parent=None): dialog = OptionsDialog(settings, parent) if settings is not None: dialog.set_settings(settings) result = dialog.exec_() if result == QDialog.Accepted: dialog.save_settings() settings.save() return result == QDialog.Accepted
class SendFundsDestination(QtWidgets.QWidget): resized_signal = QtCore.pyqtSignal() def __init__(self, parent, parent_dialog, app_config, hw_session: HwSessionInfo): QtWidgets.QWidget.__init__(self, parent) self.app_config = app_config self.parent_dialog = parent_dialog self.hw_session = hw_session self.recipients: List[SendFundsDestinationItem] = [] self.change_addresses: List[Tuple[str, str]] = [ ] # List[Tuple[address, bip32 path]] self.change_controls_visible = True self.address_widget_width = None self.inputs_total_amount = 0.0 self.fee_amount = 0.0 self.inputs_count = 0 self.values_unit = OUTPUT_VALUE_UNIT_AMOUNT self.tm_calculate_change_value = QTimer(self) self.tm_calculate_change_value.timeout.connect( self.on_tm_calculate_change_value) self.current_file_name = '' self.current_file_encrypted = False self.recent_data_files = [] # recent used data files self.setupUi(self) def setupUi(self, Form): self.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)) self.lay_main = QtWidgets.QVBoxLayout(Form) self.lay_main.setContentsMargins(6, 6, 6, 6) self.lay_main.setSpacing(3) # 'totals' area: self.lbl_totals = QLabel(Form) self.lbl_totals.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lay_main.addWidget(self.lbl_totals) # output definition data file labels: self.lay_data_file = QHBoxLayout() self.lay_data_file.setContentsMargins(0, 0, 0, 6) self.lay_main.addItem(self.lay_data_file) self.lbl_data_file_name = QLabel(Form) self.lay_data_file.addWidget(self.lbl_data_file_name) self.lbl_data_file_badge = QLabel(Form) self.lay_data_file.addWidget(self.lbl_data_file_badge) self.lbl_data_file_name.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lbl_data_file_badge.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lay_data_file.addStretch() # actions/options area: self.lay_actions = QHBoxLayout() self.lay_actions.setSpacing(6) self.lay_actions.setContentsMargins(0, 0, 0, 0) self.lay_main.addItem(self.lay_actions) self.btn_add_recipient = QPushButton(Form) self.btn_add_recipient.clicked.connect( partial(self.add_dest_address, 1)) self.btn_add_recipient.setAutoDefault(False) self.btn_add_recipient.setText("Add recipient") self.lay_actions.addWidget(self.btn_add_recipient) # self.btn_actions = QPushButton(Form) self.btn_actions.clicked.connect(partial(self.add_dest_address, 1)) self.btn_actions.setAutoDefault(False) self.btn_actions.setText("Actions") self.lay_actions.addWidget(self.btn_actions) # context menu for the 'Actions' button self.mnu_actions = QMenu() self.btn_actions.setMenu(self.mnu_actions) a = self.mnu_actions.addAction("Load from file...") a.triggered.connect(self.on_read_from_file_clicked) self.mnu_recent_files = self.mnu_actions.addMenu('Recent files') self.mnu_recent_files.setVisible(False) a = self.mnu_actions.addAction("Save to encrypted file...") a.triggered.connect(partial(self.save_to_file, True)) a = self.mnu_actions.addAction("Save to plain CSV file...") a.triggered.connect(partial(self.save_to_file, False)) a = self.mnu_actions.addAction("Clear recipients") a.triggered.connect(self.clear_outputs) self.lbl_output_unit = QLabel(Form) self.lbl_output_unit.setText('Values as') self.lay_actions.addWidget(self.lbl_output_unit) self.cbo_output_unit = QComboBox(Form) self.cbo_output_unit.addItems(['amount', 'percentage']) self.cbo_output_unit.setCurrentIndex(0) self.cbo_output_unit.currentIndexChanged.connect( self.on_cbo_output_unit_change) self.lay_actions.addWidget(self.cbo_output_unit) self.lay_actions.addStretch(0) # scroll area for send to (destination) addresses self.scroll_area = QtWidgets.QScrollArea() self.scroll_area.setWidgetResizable(True) self.scroll_area.setMinimumHeight(30) self.scroll_area.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)) self.scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame) self.lay_main.addWidget(self.scroll_area) self.scroll_area_widget = QtWidgets.QWidget() self.scroll_area_widget.setSizePolicy( QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)) self.lay_scroll_area = QtWidgets.QVBoxLayout() self.lay_scroll_area.setContentsMargins(0, 0, 0, 0) self.lay_scroll_area.setSpacing(0) self.scroll_area_widget.setLayout(self.lay_scroll_area) self.scroll_area.setWidget(self.scroll_area_widget) # grid layout for destination addresses and their corresponding controls: self.lay_addresses = QtWidgets.QGridLayout() self.lay_addresses.setSpacing(3) self.lay_addresses.setContentsMargins(0, 0, 0, 0) self.lay_scroll_area.addLayout(self.lay_addresses) self.lay_scroll_area.addStretch(0) # controls for the 'change' address/amount (it's placed in the last row of the addresses grid layout): self.lbl_change_address = QLabel(self.scroll_area_widget) self.lbl_change_address.setText('Change address') self.lbl_change_address.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.lay_addresses.addWidget(self.lbl_change_address, 0, 0) # the 'change' address combobox: self.cbo_change_address = QtWidgets.QComboBox(self.scroll_area_widget) width = self.cbo_change_address.fontMetrics().width( 'XvqNXF23dRBksxjW3VQGrBtJw7vkhWhenQ') self.address_widget_width = width + 40 # combobox width on macos needs to be tweaked: self.cbo_change_address.setFixedWidth( self.address_widget_width + {'darwin': 5}.get(sys.platform, 0)) self.lay_addresses.addWidget(self.cbo_change_address, 0, 1) self.lbl_change_amount = QLabel(self.scroll_area_widget) self.set_change_value_label() self.lay_addresses.addWidget(self.lbl_change_amount, 0, 2) # read only editbox for the amount of the change: self.edt_change_amount = QLineEdit(self.scroll_area_widget) self.edt_change_amount.setFixedWidth(100) self.edt_change_amount.setReadOnly(True) self.edt_change_amount.setStyleSheet('background-color:lightgray') self.lay_addresses.addWidget(self.edt_change_amount, 0, 3) # label dedicated to the second-unit value (e.g percentage if the main unit is set to (Dash) amount value) self.lbl_second_unit = QLabel(self.scroll_area_widget) self.lay_addresses.addWidget(self.lbl_second_unit, 0, 4) # spacer spacer = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.lay_addresses.addItem(spacer, 0, 5) # the last row of the grid layout is dedicated to 'fee' controls self.lbl_fee = QLabel(self.scroll_area_widget) self.lbl_fee.setText('Fee [Dash]') self.lbl_fee.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.lay_addresses.addWidget(self.lbl_fee, 1, 0) # the fee value editbox with the 'use default' button: self.lay_fee_value = QHBoxLayout() self.lay_fee_value.setContentsMargins(0, 0, 0, 0) self.lay_fee_value.setSpacing(0) self.lay_addresses.addItem(self.lay_fee_value, 1, 1) self.edt_fee_value = QLineEdit(self.scroll_area_widget) self.edt_fee_value.setFixedWidth(100) self.edt_fee_value.textChanged.connect(self.on_edt_fee_value_changed) self.lay_fee_value.addWidget(self.edt_fee_value) self.btn_get_default_fee = QToolButton(self.scroll_area_widget) self.btn_get_default_fee.setText('\u2b06') self.btn_get_default_fee.setFixedSize( 14, self.edt_fee_value.sizeHint().height()) self.btn_get_default_fee.setToolTip('Use default fee') self.btn_get_default_fee.clicked.connect( self.on_btn_get_default_fee_clicked) self.lay_fee_value.addWidget(self.btn_get_default_fee) self.lay_fee_value.addStretch(0) # below the addresses grid place a label dedicated do display messages self.lbl_message = QLabel(Form) self.lbl_message.setTextInteractionFlags( QtCore.Qt.LinksAccessibleByMouse | QtCore.Qt.TextSelectableByMouse) self.lbl_message.setVisible(False) self.lay_main.addWidget(self.lbl_message) # add one 'send to' address row (in most cases it will bu sufficient) self.add_dest_address(1) # load last used file names from cache mru = app_cache.get_value(CACHE_ITEM_DATA_FILE_MRU_LIST, default_value=[], type=list) if isinstance(mru, list): for file_name in mru: if os.path.exists(file_name): self.recent_data_files.append(file_name) self.update_mru_menu_items() self.retranslateUi(Form) def retranslateUi(self, Form): pass def sizeHint(self): sh = self.lay_scroll_area.sizeHint() marg_sl = self.lay_scroll_area.getContentsMargins() marg_ml = self.lay_main.getContentsMargins() if self.lbl_message.isVisible(): msg_height = self.lbl_message.height() else: msg_height = 0 sh.setHeight(sh.height() + marg_sl[1] + marg_sl[3] + self.lay_actions.sizeHint().height() + self.lbl_totals.sizeHint().height() + self.lay_data_file.sizeHint().height() + ((self.lay_main.count() - 1) * self.lay_main.spacing()) + marg_ml[1] + marg_ml[3] + msg_height) return sh def display_message(self, message, color: Optional[str] = None): if message: self.lbl_message.setText(message) if color: self.lbl_message.setStyleSheet(f'QLabel{{color:{color}}}') changed_visibility = self.lbl_message.isVisible() != True self.lbl_message.setVisible(True) else: changed_visibility = self.lbl_message.isVisible() != False self.lbl_message.setVisible(False) if changed_visibility: QtWidgets.qApp.processEvents(QEventLoop.ExcludeUserInputEvents) self.resized_signal.emit() def move_grid_layout_row(self, from_row, to_row): for col_idx in range(self.lay_addresses.columnCount()): item = self.lay_addresses.itemAtPosition(from_row, col_idx) if item: if isinstance(item, QWidgetItem): w = item.widget() self.lay_addresses.removeWidget(w) self.lay_addresses.addWidget(w, to_row, col_idx) elif isinstance(item, QLayout): self.lay_addresses.removeItem(item) self.lay_addresses.addItem(item, to_row, col_idx) elif isinstance(item, QSpacerItem): self.lay_addresses.removeItem(item) self.lay_addresses.addItem(item, to_row, col_idx) else: raise Exception('Invalid item type') def add_dest_address(self, address_count: int = 1): # make a free space in the grid-layout for new addresses, just behind the last item related to the dest address for row_idx in reversed( range(len(self.recipients), self.lay_addresses.rowCount())): self.move_grid_layout_row(row_idx, row_idx + address_count) for nr in range(address_count): rcp_item = SendFundsDestinationItem(self.scroll_area_widget, self.app_config, self.lay_addresses, len(self.recipients), self.address_widget_width) rcp_item.sig_remove_address.connect(self.remove_dest_address) rcp_item.sig_use_all_funds.connect(self.use_all_funds_for_address) rcp_item.sig_amount_changed.connect(self.amount_changed) rcp_item.set_output_value_unit(self.values_unit) rcp_item.set_inputs_total_amount(self.inputs_total_amount - self.fee_amount) self.recipients.append(rcp_item) QtWidgets.qApp.processEvents(QEventLoop.ExcludeUserInputEvents) self.resized_signal.emit() self.show_hide_remove_buttons() self.display_totals() self.set_default_fee() def remove_item_from_layout(self, item): if item: if isinstance(item, QWidgetItem): w = item.widget() self.lay_addresses.removeWidget(w) w.setParent(None) del w elif isinstance(item, QLayout): for subitem_idx in reversed(range(item.count())): subitem = item.itemAt(subitem_idx) self.remove_item_from_layout(subitem) self.lay_addresses.removeItem(item) item.setParent(None) del item elif isinstance(item, QSpacerItem): del item else: raise Exception('Invalid item type') def remove_dest_address(self, address_item): row_idx = self.recipients.index(address_item) # remove all widgets related to the 'send to' address that is being removed for col_idx in range(self.lay_addresses.columnCount()): item = self.lay_addresses.itemAtPosition(row_idx, col_idx) self.remove_item_from_layout(item) # move up all rows greater than the row being removed for row in range(row_idx + 1, len(self.recipients)): self.move_grid_layout_row(row, row - 1) del self.recipients[row_idx] QtWidgets.qApp.processEvents(QEventLoop.ExcludeUserInputEvents) self.resized_signal.emit() self.show_hide_remove_buttons() self.set_default_fee() # self.calculate_change_amount() self.display_totals() def use_all_funds_for_address(self, address_item): row_idx = self.recipients.index(address_item) sum = 0.0 left = 0.0 # sum all the funds in all rows other than the current one for idx, addr in enumerate(self.recipients): if idx != row_idx: sum += addr.get_value(default_value=0.0) if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: left = self.inputs_total_amount - sum - self.fee_amount elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: left = 100.0 - sum left = round(left, 8) + 0.0 if left < 0: left = 0.0 address_item.set_value(left) self.update_change_amount() def amount_changed(self, addres_item): """ Activated after changing value in the 'amount' edit box of a recipient address. """ self.init_calculate_change_value() def on_edt_fee_value_changed(self, text): if not text: text = '0.0' try: self.fee_amount = float(text) self.init_calculate_change_value() except Exception: self.display_message('Invalid \'transaction fee\' value.', 'red') # display error message def show_hide_change_address(self, visible): if visible != self.change_controls_visible: row_nr = self.lay_addresses.rowCount() - 1 if row_nr >= 0: for col_idx in range(self.lay_addresses.columnCount()): item = self.lay_addresses.itemAtPosition(row_nr, col_idx) if item: if isinstance(item, QWidgetItem): item.widget().setVisible(visible) elif isinstance( item, (QSpacerItem, QHBoxLayout, QVBoxLayout)): pass else: raise Exception('Invalid item type') self.change_controls_visible = visible QtWidgets.qApp.processEvents(QEventLoop.ExcludeUserInputEvents) self.resized_signal.emit() def show_hide_remove_buttons(self): visible = len(self.recipients) > 1 for item in self.recipients: item.set_btn_remove_address_visible(visible) def set_change_addresses(self, addresses: List[Tuple[str, str]]): """ :param addresses: addresses[0]: dest change address addresses[1]: dest change bip32 :return: """ self.cbo_change_address.clear() self.change_addresses.clear() for addr in addresses: self.cbo_change_address.addItem(addr[0]) self.change_addresses.append((addr[0], addr[1])) def set_input_amount(self, amount, inputs_count): self.inputs_count = inputs_count if amount != self.inputs_total_amount or inputs_count != self.inputs_count: # if there is only one recipient address and his current amount equals to the # previuus input_amount, assign new value to him last_total_amount = self.inputs_total_amount last_fee_amount = self.fee_amount self.inputs_total_amount = amount self.fee_amount = self.calculate_fee() if (len(self.recipients) == 1 or self.recipients[0].get_value(default_value=0.0) == 0.0 or self.recipients[0].get_value(default_value=0.0) == round(last_total_amount - last_fee_amount, 8)): if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: amount_minus_fee = round(amount - self.fee_amount, 8) if amount_minus_fee < 0: amount_minus_fee = 0.0 self.recipients[0].set_value(amount_minus_fee) elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: self.recipients[0].set_value(100.0) old_state = self.edt_fee_value.blockSignals(True) self.edt_fee_value.setText(app_utils.to_string(self.fee_amount)) self.edt_fee_value.blockSignals(old_state) for addr in self.recipients: addr.set_inputs_total_amount(amount - self.fee_amount) addr.clear_validation_results() self.edt_fee_value.update() self.update_change_amount() self.display_totals() def calculate_change_value(self) -> float: """Returns the change value in Dash.""" sum = 0.0 for addr in self.recipients: sum += addr.get_value(default_value=0.0) if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: change_amount = round( self.inputs_total_amount - sum - self.fee_amount, 8) + 0 # eliminate -0.0 elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: change_amount = round( (100.0 - sum) * (self.inputs_total_amount - self.fee_amount) / 100, 8) else: raise Exception('Invalid unit') return change_amount def update_change_amount(self) -> None: change_amount = self.calculate_change_value() if self.inputs_total_amount - self.fee_amount != 0: change_pct = round( change_amount * 100 / (self.inputs_total_amount - self.fee_amount), 8) + 0 else: change_pct = 0.0 if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: left_second_unit_str = app_utils.to_string(round(change_pct, 3)) + '%' self.edt_change_amount.setText( app_utils.to_string(round(change_amount, 8))) elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: left_second_unit_str = app_utils.to_string(round(change_amount, 8)) + ' Dash' sum = 0.0 for addr in self.recipients: sum += addr.get_value(default_value=0.0) self.edt_change_amount.setText( app_utils.to_string(round(100 - sum, 8))) else: raise Exception('Invalid unit') msg = '' if change_amount < 0: used_amount = round(self.inputs_total_amount - change_amount, 8) + 0 msg = f'Not enough funds - used amount: ' \ f'{used_amount}, available: {self.inputs_total_amount}. Adjust ' \ f'the output values.' self.lbl_second_unit.setText(left_second_unit_str) self.display_message(msg, 'red') def validate_output_data(self) -> bool: ret = True for addr in self.recipients: if not addr.validate(): ret = False if not ret: self.display_message( 'Data of at least one recipient is invalid or empty. ' 'Please correct the data to continue.', 'red') else: self.display_message('') return ret def on_tm_calculate_change_value(self): self.tm_calculate_change_value.stop() for addr_item in self.recipients: addr_item.set_inputs_total_amount(self.inputs_total_amount - self.fee_amount) self.update_change_amount() def calculate_fee(self): if self.inputs_total_amount > 0.0: bytes = (self.inputs_count * 148) + (len(self.recipients) * 34) + 10 fee = round(bytes * FEE_SAT_PER_BYTE, 8) if not fee: fee = MIN_TX_FEE fee = round(fee / 1e8, 8) else: fee = 0.0 return fee def get_tx_fee(self): if self.fee_amount < 0.0: raise Exception('Invalid the fee value.') return round(self.fee_amount * 1e8) def init_calculate_change_value(self): self.tm_calculate_change_value.start(100) def set_default_fee(self): self.fee_amount = self.calculate_fee() old_status = self.edt_fee_value.blockSignals(True) try: self.edt_fee_value.setText(app_utils.to_string(self.fee_amount)) finally: self.edt_fee_value.blockSignals(old_status) self.edt_fee_value.update() self.init_calculate_change_value() def on_btn_get_default_fee_clicked(self): self.set_default_fee() def set_dest_addresses(self, addresses: List): if len(addresses) > 0: count_diff = len(addresses) - len(self.recipients) if count_diff > 0: self.add_dest_address(count_diff) elif count_diff < 0: # remove unecessary rows, beginning from the largest one for nr in reversed(range(len(addresses), len(self.recipients))): self.remove_dest_address(self.recipients[nr]) for idx, addr_item in enumerate(self.recipients): if isinstance(addresses[idx], (list, tuple)): # passed address-value tuple if len(addresses[idx]) >= 1: addr_item.set_address(addresses[idx][0]) if len(addresses[idx]) >= 2: addr_item.set_value(addresses[idx][1]) else: addr_item.set_address(addresses[idx]) self.display_totals() def set_change_value_label(self): if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: self.lbl_change_amount.setText('value') self.lbl_change_amount.setToolTip( 'Unused amount - will be sent back to the change address') else: self.lbl_change_amount.setText('pct. value') self.lbl_change_amount.setToolTip( 'Unused amount (as percent of the total value of all inputs) - will ' 'be sent back to the change address') def on_cbo_output_unit_change(self, index): if index == 0: self.values_unit = OUTPUT_VALUE_UNIT_AMOUNT else: self.values_unit = OUTPUT_VALUE_UNIT_PERCENT self.set_change_value_label() for addr_item in self.recipients: addr_item.set_output_value_unit(self.values_unit) self.update_change_amount() def update_ui_value_unit(self): if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: self.cbo_output_unit.setCurrentIndex(0) else: self.cbo_output_unit.setCurrentIndex(1) def simplyfy_file_home_dir(self, file_name): home_dir = os.path.expanduser('~') if self.current_file_name.find(home_dir) == 0: file_name = '~' + self.current_file_name[len(home_dir):] else: file_name = self.current_file_name return file_name def display_totals(self): bytes = (self.inputs_count * 148) + (len(self.recipients) * 34) + 10 text = f'<span class="label"><b>Total value of selected inputs:</b> </span><span class="value"> {self.inputs_total_amount} Dash </span>' if self.inputs_total_amount > 0: text += f'<span class="label"> <b>Inputs:</b> </span><span class="value"> {self.inputs_count} </span>' \ f'<span class="label"> <b>Outputs:</b> </span><span class="value"> {len(self.recipients)} </span>' \ f'<span class="label"> <b>Transaction size:</b> </span><span class="value"> {bytes} B </span>' self.lbl_totals.setText(text) if self.current_file_name: file_name = self.simplyfy_file_home_dir(self.current_file_name) text = f'<span class="label"><b>File:</b> </span><span class="value">{file_name} </span>' self.lbl_data_file_name.setText(text) self.lbl_data_file_name.setVisible(True) self.lbl_data_file_badge.setVisible(True) if self.current_file_encrypted: self.lbl_data_file_badge.setText('Encrypted') self.lbl_data_file_badge.setStyleSheet( "QLabel{background-color:#2eb82e;color:white; padding: 1px 3px 1px 3px; border-radius: 3px;}" ) else: self.lbl_data_file_badge.setText('Not encrypted') self.lbl_data_file_badge.setStyleSheet( "QLabel{background-color:orange;color:white; padding: 1px 3px 1px 3px; border-radius: 3px;}" ) else: self.lbl_data_file_name.setVisible(False) self.lbl_data_file_badge.setVisible(False) def clear_outputs(self): if WndUtils.queryDlg("Do you really want to clear all outputs?", default_button=QMessageBox.Cancel, icon=QMessageBox.Warning) == QMessageBox.Ok: self.set_dest_addresses([('', '')]) self.use_all_funds_for_address(self.recipients[0]) def save_to_file(self, save_encrypted): if self.current_file_name and os.path.exists( os.path.dirname(self.current_file_name)): dir = os.path.dirname(self.current_file_name) else: dir = self.app_config.data_dir if save_encrypted: initial_filter = "DAT files (*.dat)" else: initial_filter = "CSV files (*.csv)" file_filter = f"{initial_filter};;All Files (*)" file_name = WndUtils.save_file_query( self.parent_dialog, message='Enter the file name to save the data.', directory=dir, filter=file_filter, initial_filter=initial_filter) if file_name: data = bytes() data += b'RECIPIENT_ADDRESS\tVALUE\n' if self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: suffix = '%' else: suffix = '' for addr in self.recipients: line = f'{addr.get_address()}{CSV_SEPARATOR}{str(addr.get_value(default_value=""))}{suffix}\n' data += line.encode('utf-8') if save_encrypted: write_file_encrypted(file_name, self.hw_session, data) else: with open(file_name, 'wb') as f_ptr: f_ptr.write(data) self.current_file_name = file_name self.current_file_encrypted = save_encrypted self.add_menu_item_to_mru(self.current_file_name) self.update_mru_menu_items() self.display_totals() def on_read_from_file_clicked(self): try: if self.current_file_name and os.path.exists( os.path.dirname(self.current_file_name)): dir = os.path.dirname(self.current_file_name) else: dir = self.app_config.data_dir initial_filter1 = "DAT files (*.dat)" initial_filter2 = "CSV files (*.csv)" file_filter = f"{initial_filter1};;{initial_filter2};;All Files (*.*)" file_name = WndUtils.open_file_query( self.parent_dialog, message='Enter the file name to read the data.', directory=dir, filter=file_filter, initial_filter='All Files (*.*)') if file_name: self.read_from_file(file_name) except Exception as e: self.parent_dialog.errorMsg(str(e)) def read_from_file(self, file_name): try: file_info = {} data_decrypted = bytearray() for block in read_file_encrypted(file_name, file_info, self.hw_session): data_decrypted.extend(block) file_encrypted = file_info.get('encrypted', False) data = data_decrypted.decode('utf-8') addresses = [] value_unit = None for line_idx, line in enumerate(data.split('\n')): if line: elems = line.split('\t') if len(elems) < 2: elems = line.split(';') if len(elems) < 2: raise ValueError( f'Invalid data file entry for line: {line_idx+1}.') address = elems[0].strip() value = elems[1].strip() address_valid = dash_utils.validate_address( address, dash_network=None) if not address_valid: if line_idx == 0 and re.match(r'^[A-Za-z_]+$', address): continue # header line else: raise ValueError( f'Invalid recipient address ({address}) (line {line_idx+1}).' ) if value.endswith('%'): vu = OUTPUT_VALUE_UNIT_PERCENT value = value[:-1] else: vu = OUTPUT_VALUE_UNIT_AMOUNT if value_unit is None: value_unit = vu elif value_unit != vu: raise ValueError( f'The value unit in line {line_idx+1} differs from the previous ' f'line.') try: if value: value = float(value.replace(',', '.')) else: value = None addresses.append((address, value)) except Exception as e: raise ValueError( f'Invalid data in the \'value\' field (line {line_idx+1}).' ) if len(addresses) == 0: raise Exception('File doesn\'t contain any recipient\'s data.') else: if self.values_unit != value_unit: self.values_unit = value_unit self.update_ui_value_unit() self.set_dest_addresses(addresses) self.current_file_name = file_name self.current_file_encrypted = file_encrypted self.add_menu_item_to_mru(self.current_file_name) self.update_mru_menu_items() self.update_change_amount() self.display_totals() except Exception as e: self.update_mru_menu_items() logging.exception( 'Exception while reading file with recipients data.') self.parent_dialog.errorMsg(str(e)) def add_menu_item_to_mru(self, file_name: str) -> None: if file_name: try: if file_name in self.recent_data_files: idx = self.recent_data_files.index(file_name) del self.recent_data_files[idx] self.recent_data_files.insert(0, file_name) else: self.recent_data_files.insert(0, file_name) app_cache.set_value(CACHE_ITEM_DATA_FILE_MRU_LIST, self.recent_data_files) except Exception as e: logging.warning(str(e)) def update_mru_menu_items(self): app_utils.update_mru_menu_items(self.recent_data_files, self.mnu_recent_files, self.on_data_file_mru_action_triggered, self.current_file_name, self.on_act_clear_mru_items) def on_act_clear_mru_items(self): self.recent_data_files.clear() app_cache.set_value(CACHE_ITEM_DATA_FILE_MRU_LIST, self.recent_data_files) self.update_mru_menu_items() def on_data_file_mru_action_triggered(self, file_name: str) -> None: """ Triggered by clicking one of the subitems of the 'Open Recent' menu item. Each subitem is related to one of recently openend data files. :param file_name: A data file name accociated with the menu action clicked. """ self.read_from_file(file_name) def get_tx_destination_data(self) -> List[Tuple[str, int, str]]: """ :return: Tuple structure: [0]: dest address [1]: value in satoshis/duffs [2]: bip32 path of the address if the item is a change address, otherwise None """ if self.validate_output_data(): change_amount = self.calculate_change_value() if change_amount < 0.0: self.update_change_amount( ) # here an appropriate message will be displayed raise Exception('Not enough funds!!!') dest_data = [] for addr in self.recipients: dest_addr = addr.get_address() value = round(addr.get_value_amount() * 1e8) dest_data.append((dest_addr, value, None)) if change_amount > 0.0: change_address_idx = self.cbo_change_address.currentIndex() if change_address_idx >= 0 and change_address_idx < len( self.change_addresses): dest_data.append( (self.change_addresses[change_address_idx][0], round(change_amount * 1e8), self.change_addresses[change_address_idx][1])) else: raise Exception('Invalid address for the change.') return dest_data else: return [] def get_recipients_list(self) -> List[Tuple[str, ]]: """ :return: List of recipient addresses List[Tuple[str <address>, float <value>] """ dest_data = [] for addr in self.recipients: dest_addr = addr.get_address() if dest_addr: dest_data.append((dest_addr, )) return dest_data
class QOutputConfig(QWidget): contentsModified = pyqtSignal() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) self.trackTable = OutputTrackList(self) self.trackTable.contentsModified.connect(self.isModified) layout.addWidget(self.trackTable) self.titleLabel = QLabel("Title:", self) self.titleEdit = QLineEdit(self) self.titleEdit.textChanged.connect(self.setOutputTitle) sublayout = QHBoxLayout() sublayout.addWidget(self.titleLabel) sublayout.addWidget(self.titleEdit) layout.addLayout(sublayout) self.fileLabel = QLabel("File name:", self) self.fileEdit = QLineEdit(self) self.fileEdit.textChanged.connect(self.setOutputPath) self.browseBtn = QPushButton(self) self.browseBtn.clicked.connect(self.execBrowseDlg) self.browseBtn.setIcon(QIcon.fromTheme("document-open")) sublayout = QHBoxLayout() sublayout.addWidget(self.fileLabel) sublayout.addWidget(self.fileEdit) sublayout.addWidget(self.browseBtn) layout.addLayout(sublayout) sublayout = QHBoxLayout() self.targetSizeCheckBox = QCheckBoxMatchHeight( "&Target File Size (Overrides settings in video encoder)", self) self.targetSizeSpinBox = QDoubleSpinBox(self) self.targetSizeSpinBox.setMinimum(1) self.targetSizeSpinBox.setDecimals(3) self.targetSizeSpinBox.setMaximum(65536) self.targetSizeSpinBox.setSuffix(" MB") self.targetSizeCheckBox.setHeightMatch(self.targetSizeSpinBox) self.targetSizeCheckBox.stateChanged.connect(self.setTargetSizeMode) self.targetSizeSpinBox.valueChanged.connect(self.setTargetSize) sublayout.addWidget(self.targetSizeCheckBox) sublayout.addWidget(self.targetSizeSpinBox) sublayout.addStretch() layout.addLayout(sublayout) self.settingsBtn = QPushButton("Con&figure Container...", self) self.settingsBtn.setIcon(QIcon.fromTheme("preferences-other")) self.settingsBtn.clicked.connect(self.configureContainer) self.btnlayout = QHBoxLayout() self.btnlayout.addStretch() self.btnlayout.addWidget(self.settingsBtn) layout.addLayout(self.btnlayout) self.setOutputFile(None) def setOutputTitle(self, title): self.output_file.title = title self.isModified() def setOutputPath(self, path): self.output_file.outputpathrel = path self.isModified() def setTargetSize(self, value): if self.targetSizeCheckBox.checkState(): self.output_file.targetsize = value * 1024**2 self.isModified() def setTargetSizeMode(self, flag): self.targetSizeSpinBox.setVisible(flag) if flag: self.output_file.targetsize = (self.targetSizeSpinBox.value() * 1024**2) else: self.output_file.targetsize = None self.isModified() def execBrowseDlg(self): exts = ' '.join(f'*{ext}' for ext in self.output_file.extensions) filters = (f"{self.output_file.fmtname} Files ({exts})") if self.output_file.config and self.output_file.config.workingdir: fileName = os.path.join(self.output_file.config.workingdir, self.fileEdit.text()) else: fileName = self.fileEdit.text() fileName, _ = QFileDialog.getSaveFileName(self, "Save File", fileName, filters) if fileName: if (self.output_file.config and self.output_file.config.workingdir): fileName = os.path.join(self.output_file.config.workingdir, fileName) if not os.path.relpath( fileName, self.output_file.config.workingdir).startswith("../"): fileName = os.path.relpath( fileName, self.output_file.config.workingdir) self.fileEdit.setText(fileName) self.isModified() return True return False def isModified(self): self._modified = True self.contentsModified.emit() def _resetMinimumSize(self): self.targetSizeSpinBox.setMinimum(self.output_file.minimumSize() / 1024**2) def notModified(self): self._modified = False def modified(self): return self._modified def updateOutputPath(self): self.fileEdit.blockSignals(True) if (isinstance(self.output_file, BaseWriter) and self.output_file.outputpathrel): self.fileEdit.setText(self.output_file.outputpathrel or "") else: self.fileEdit.setText("") self.fileEdit.blockSignals(False) def setOutputFile(self, output_file=None): self.notModified() self.output_file = output_file if output_file is not None: self.trackTable.setOutputFile(output_file) self.trackTable.contentsModified.connect(self._resetMinimumSize) self.titleEdit.blockSignals(True) self.titleEdit.setText(output_file.title or "") self.titleEdit.blockSignals(False) self.updateOutputPath() self.targetSizeCheckBox.blockSignals(True) self.targetSizeCheckBox.setTristate(False) self.targetSizeCheckBox.setCheckState( 2 if output_file.targetsize is not None else 0) self.targetSizeCheckBox.blockSignals(False) self.targetSizeSpinBox.setHidden(output_file.targetsize is None) if output_file.targetsize: output_file.loadOverhead() self.targetSizeSpinBox.blockSignals(True) self._resetMinimumSize() self.targetSizeSpinBox.setValue(output_file.targetsize / 1024**2) self.targetSizeSpinBox.blockSignals(False) self.settingsBtn.setEnabled(output_file.QtDlgClass() is not None) self.settingsBtn.setText(f"{output_file.fmtname} Options...") else: self.titleEdit.blockSignals(True) self.titleEdit.setText("") self.titleEdit.blockSignals(False) self.fileEdit.blockSignals(True) self.fileEdit.setText("") self.fileEdit.blockSignals(False) self.trackTable.setOutputFile(None) self.settingsBtn.setEnabled(False) self.settingsBtn.setText("Options...") self.targetSizeCheckBox.blockSignals(True) self.targetSizeCheckBox.setTristate(True) self.targetSizeCheckBox.setCheckState(1) self.targetSizeCheckBox.blockSignals(False) self.targetSizeSpinBox.setHidden(True) self.setEnabled(output_file is not None) def configureContainer(self): dlg = self.output_file.QtDlg(self) if dlg is not None: dlg.settingsApplied.connect(self.contentsModified) dlg.exec_() self._resetMinimumSize()
class RequestNavigation(PWidget): alert = pyqtSignal(str, str, object, object) currentChange = pyqtSignal() def __init__(self): super().__init__() # self.setMaximumWidth(600) self.setObjectName(Parapluie.Object_Raised_Off) self.backButton = QToolButton() self.backButton.setIcon( PResource.defaultIcon(Parapluie.Icon_Left_Arrow_Svg)) self.backButton.setFixedSize(36, 36) self.backButton.pressed.connect(self.onBackPressed) self.searchBar = QLineEdit() self.searchBar.setPlaceholderText("Search..") self.searchBar.textChanged.connect(self.searchFile) self.searchBar.setFixedHeight(36) self.addButton = QToolButton() self.addButton.setIcon(PResource.defaultIcon(Parapluie.Icon_Plus_Svg)) self.addButton.setFixedSize(36, 36) self.addButton.pressed.connect(self.newFile) self.openButton = QToolButton() self.openButton.setIcon( PResource.defaultIcon(Parapluie.Icon_Folder_Svg)) self.openButton.setFixedSize(36, 36) self.openButton.pressed.connect(self.openFile) topBar = QHBoxLayout() topBar.addWidget(self.backButton) topBar.addWidget(self.searchBar) topBar.addWidget(self.openButton) topBar.addWidget(self.addButton) self.listDataWidget = QListWidget() self.listDataWidget.setContentsMargins(0, 0, 0, 0) # self.listDataWidget.setObjectName(Parapluie.Object_Raised) self.listDataWidget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.listDataWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.listDataWidget.horizontalScrollBar().setEnabled(False) self.listDataWidget.setSpacing(3) self.gridData = PGridWidget(2) # self.gridData.setObjectName(Parapluie.Object_Raised) self.gridData.setContentsMargins(0, 0, 0, 0) self.gridData.setHorizontalSpacing(5) self.gridData.setVerticalSpacing(5) self.gridData.setFixColumn(True) self.gridData.setRowHeight(100) self.gridData.setAlignment(Qt.AlignTop | Qt.AlignLeft) self.stacked = QStackedWidget() self.stacked.addWidget(self.gridData) self.stacked.addWidget(self.listDataWidget) self.stacked.currentChanged.connect(self.tabChange) layout = QVBoxLayout() layout.addLayout(topBar) layout.addWidget(self.stacked) layout.setContentsMargins(8, 8, 0, 8) self.setLayout(layout) self.categoriesChange = None self.dataList = [] # combine self.categories = [] # string self.dataSource = XashList() self.dataTemp = [] # xfile self.listFile = [] # xfile self.currentFile = None self.newCount = -1 self.lastSearch = {1: "", 0: ""} self.listAdapter = ItemAdapter(self, self.listDataWidget, self.dataList, self.dataSource, self.onItemSelected, self.onItemClosed) gridAdapter = GridAdapter(data=self.categories) gridAdapter.setOnItemClick(self.onCategoriesClicked) self.gridData.setAdapter(gridAdapter) self.stacked.setCurrentIndex(1) self.tabChange(1) self.loadFile() def loadFile(self): self.dataSource.clear() self.dataSource.load(Config.getRequestFolder()[0]) self.updateCategories(self.dataSource.list) self.updateDataList(self.dataTemp, self.dataSource.list) self.listAdapter.refresh() self.gridData.refresh() def updateCategories(self, lstSrc): self.categories.clear() self.categories.append("all") self.categories.append('recent') for data in lstSrc: c = data.categories.category if data.categories is not None else "" if c != "" and c not in self.categories: self.categories.append(c) if self.categoriesChange is not None: self.categoriesChange(self.categories) def searchFile(self, a0: str): if self.stacked.currentIndex() == 1: self.lastSearch[1] = a0 if a0 == "" or a0 == "categories:all": self.updateDataList(self.dataTemp, self.dataSource.list) elif a0 == 'categories:recent': self.updateDataList(self.dataTemp, []) else: if a0.startswith("categories:"): self.updateDataList([], self.dataSource.findWithCategory( a0.replace("categories:", ""))) elif a0.startswith("name:"): self.updateDataList([], self.dataSource.findWithName( a0.replace("name:", ""))) else: self.updateDataList([], self.dataSource.findEveryWhere( "", a0, "description#" + a0)) self.listAdapter.refresh() else: self.lastSearch[0] = a0 self.categories.clear() if a0 == "": self.categories.append("all") self.categories.append('recent') for data in self.dataSource.list: d: Xash = data c = d.categories.category if d.categories is not None else "" if c != "" and c not in self.categories: self.categories.append(c) else: for data in self.dataSource.list: d: Xash = data c = d.categories.category if d.categories is not None else "" if c != "" and c not in self.categories and a0 in c: self.categories.append(c) self.gridData.refresh() def tabChange(self, index): if index == 1: self.backButton.setVisible(True) self.searchBar.setText(self.lastSearch[1]) self.searchBar.blockSignals(False) else: self.backButton.setVisible(False) self.searchBar.setText(self.lastSearch[0]) def onCategoriesClicked(self, txt): self.stacked.setCurrentIndex(1) self.searchBar.setText("categories:" + txt) def onBackPressed(self): self.stacked.setCurrentIndex(0) def resizeEvent(self, a0: QtGui.QResizeEvent): super(RequestNavigation, self).resizeEvent(a0) col = int(self.width() / 150) + 1 self.gridData.setNumberColumn(col) def refresh(self): self.listAdapter.refresh() self.gridData.refresh() def newFile(self): self.newCount += 1 file = XFile("Unsaved/Untitled-" + str(self.newCount)) self.dataTemp.insert(0, file) self.onItemSelected(file) self.updateDataList(self.dataTemp, self.dataSource.list) self.refresh() def pushAlert(self, text, tpe=Parapluie.Alert_Error): self.alert.emit(text, tpe, None, None) def openFile(self): init_dir = Config.getRequestConfig_LastOpen() name = QFileDialog.getOpenFileName( self, 'Open file', init_dir, "XuCompa Request (*.xreq);; JSON files (*.json);; All files (*)") if name[0] != "": config = Config.getConfig() config["viewer"]["last_open"] = os.path.dirname(name[0]) Config.updateConfig(config) file = XFile(name[0]) self.dataTemp.insert(0, file) self.onItemSelected(file) self.updateDataList(self.dataTemp, self.dataSource.list) self.refresh() def updateDataList(self, lstNew, lstSrc): self.dataList.clear() if len(lstNew): # header item item = ItemModel() item.file = "Recent File" item.selected = -1 self.dataList.append(item) for data in lstNew: # XFile item = ItemModel() item.file = data item.selected = 1 if self.currentFile == data else 0 self.dataList.append(item) if len(lstSrc) > 0: # header item item = ItemModel() item.file = "Request Folder" item.selected = -1 self.dataList.append(item) for data in lstSrc: # Xash item = ItemModel() if data.getPath() in self.listFile: inx = self.listFile.index(data.getPath()) file = self.listFile[inx] else: file = XFile(data.getPath()) self.listFile.append(file) item.file = file item.selected = 1 if self.currentFile == file else 0 self.dataList.append(item) def onItemSelected(self, data): if isinstance(data, XFile): self.currentFile = data self.currentChange.emit() for item in self.dataList: if item.file == data: item.selected = 1 else: item.selected = 0 self.refresh() def onItemClosed(self, data): if self.currentFile == data: self.currentFile = None self.currentChange.emit() if isinstance(data, XFile): if inRequestFolder(data.getPath()): message = PMessage( self, QRect(QPoint(0, 0), QApplication.focusWindow().screen().size())) Utilities.Style.applyWindowIcon(message) message.initWarning("Are you want delete this item!", negative="Delete") code = message.exec_() if code == 1: # remove description xash = XashHelp.path2Xash(data.getPath()) xashId = xash.getId() self.saveDescription("", xashId, True) # remove file os.remove(data.getPath()) if data in self.dataList: self.dataList.remove(data) if data in self.dataTemp: self.dataTemp.remove(data) self.loadFile() def saveData(self, file: XFile, newData): if file.unsavedData is not None: # has data unsaved if inRequestFolder(file.getPath()): self.saveExistedInRequest(file, newData) else: self.saveNewFile(file, newData) def getSavePath(self, name, category, description) -> str: # create new xashID xashId = XashHelp.newXashId(category, self.dataSource) # write description descId = self.saveDescription(description, xashId) # gotta new xash string x = XashHelp.createXash(name, xashId, {'description': descId}, '.xreq') # join with folder path = os.path.join(Config.getRequestFolder()[0], x) return path def saveDescription(self, desc, descId=None, isRemove=False): # read descriptions xDef = Config.getRequestFolder()[1] if os.path.isfile(xDef): file = open(xDef, "r", encoding='utf-8') data = file.read() file.close() else: data = '' # edit listDesc = data.split("\n") if descId is None and not isRemove: newId = '[desc#%s]' % str(len(listDesc)) lines = newId + 'xreq_description--description#' + desc listDesc.append(lines) else: newId = descId exist = False for d in listDesc: if d.startswith(newId): inx = listDesc.index(d) if not isRemove: listDesc[ inx] = newId + 'xreq_description--description#' + desc exist = True else: listDesc.remove(d) break if not exist: lines = newId + 'xreq_description--description#' + desc listDesc.append(lines) # save file = open(xDef, "w", encoding='utf-8') data = "\n".join(listDesc) file.write(data) file.close() return newId def saveNewFile(self, file: XFile, newData): temp = self.dataSource.findMatchAll(newData[1], newData[0], "", sensitive=False) if len(temp) > 0: self.pushAlert("File was existed!!!") else: # data text = Formatter.dumps(file.unsavedData.data, EditorType.JSON, self.pushAlert) path = self.getSavePath(newData[0], newData[1], newData[2]) if self.save(file, path, text): if file in self.dataTemp: self.dataTemp.remove(file) self.loadFile() def saveExistedInRequest(self, file: XFile, newData: tuple): # data text = Formatter.dumps(file.unsavedData.data, EditorType.JSON, self.pushAlert) xash = XashHelp.path2Xash(file.getPath()) if xash.getCategory() == newData[1] and xash.getName() == newData[0]: # replace existed description xashId = xash.getId() self.saveDescription(newData[2], xashId) if self.save(file, file.getPath(), text): self.loadFile() else: temp = self.dataSource.findMatchAll(newData[1], newData[0], "", sensitive=False) if len(temp) > 0: self.pushAlert("File was existed!!!") else: message = PMessage( self, QRect(QPoint(0, 0), QApplication.focusWindow().screen().size())) message.initQuestion( "File is saved, do you want to rename or create new file?", [{ "text": "Rename", 'type': Parapluie.Button_Negative }, { "text": "New File", 'type': Parapluie.Button_Positive }, { "text": "Close", 'type': Parapluie.Button_Neutral }], 'Save request') code = message.exec_() if code == 0: # remove old description xashId = xash.getId() self.saveDescription(newData[2], xashId, True) path = self.getSavePath(newData[0], newData[1], newData[2]) os.rename(file.getPath(), path) if self.save(file, path, text): self.loadFile() elif code == 1: path = self.getSavePath(newData[0], newData[1], newData[2]) if self.save(file, path, text): self.loadFile() def save(self, file: XFile, path, text) -> bool: try: f = open(path, 'w', encoding='utf-8') f.write(text) f.close() file.unsavedData = None file.setPath(path) file.data = XashHelp.path2Xash(path) self.pushAlert("Saved!!!", Parapluie.Alert_Success) return True except Exception as ex: logging.exception(ex) self.pushAlert(str(ex)) return False
class double_thresold(QWidget): from_slider_min = pyqtSignal(str) from_slider_max = pyqtSignal(str) from_line_min = pyqtSignal(int) from_line_max = pyqtSignal(int) from_label_min = pyqtSignal(int) from_label_max = pyqtSignal(int) def __init__(self, img_path='1023.png'): super().__init__() self.minValue = 100 self.maxValue = 200 self.press = 0 self.now = 0 self.img_path = img_path self.init_ui() # self.histpath(self.img_path) # 初始化 def init_ui(self): # 设置窗口大小,标题 self.setFixedSize(750, 300) self.setWindowModality(Qt.ApplicationModal) self.move(300, 300) self.setWindowTitle("Double Threshold") # 定义框架,主框架下有左右两个盒式框架,左侧框架中是堆叠框架,实现控件的堆叠,右侧框架(纵向排列)中有两个横向排列盒式框架,存放标签、文本框和滑块 self.main_box = QHBoxLayout() self.left1 = QVBoxLayout() self.stack = QStackedLayout() self.right1 = QVBoxLayout() self.right1_1 = QHBoxLayout() self.right1_2 = QHBoxLayout() # 定义控件,F为嵌入的matplotlib图像 try: self.F = MyFigure() # self.F.hist(self.img_path) self.F.setMinimumWidth(500) self.F.setMaximumWidth(500) self.right1.setSpacing(10) self.right1_1.setSpacing(10) self.right1_2.setSpacing(10) self.label1_1 = QLabel("Min Val:") self.lineEdit1_1 = QLineEdit("100") self.lineEdit1_1.setMaximumWidth(70) self.slider1_1 = QSlider(Qt.Horizontal) self.slider1_1.setFocusPolicy(Qt.NoFocus) self.slider1_1.setRange(0, 255) self.slider1_1.setValue(100) self.label1_2 = QLabel("Max Val:") self.lineEdit1_2 = QLineEdit("200") self.lineEdit1_2.setMaximumWidth(70) self.slider1_2 = QSlider(Qt.Horizontal) self.slider1_2.setFocusPolicy(Qt.NoFocus) self.slider1_2.setRange(0, 255) self.slider1_2.setValue(200) self.button1_3 = QPushButton("Apply to Image") # 用label和F的堆叠实现用蓝色色块标注当前选中区域的功能 self.mask = QLabel() # 堆叠框架只能间接添加框架 self.mask_layout_widget = QWidget() self.mask_layout = QHBoxLayout() self.mask_layout.addWidget(self.mask) self.mask_layout_widget.setLayout(self.mask_layout) # 绑定控件的事件 self.slider1_1.valueChanged.connect(self.change_sld_min_value) self.slider1_2.valueChanged.connect(self.change_sld_max_value) self.lineEdit1_1.editingFinished.connect( self.change_line_min_value_finished) self.lineEdit1_2.editingFinished.connect( self.change_line_max_value_finished) self.mask.mousePressEvent = self.label_mouse_press_event self.mask.mouseMoveEvent = self.label_mouse_move_event except Exception as err: print("set widgets:{0}".format(err)) # 把控件添加到框架中 try: self.left1.addLayout(self.stack) self.stack.addWidget(self.F) self.stack.addWidget(self.mask_layout_widget) self.right1_1.addWidget(self.label1_1) self.right1_1.addWidget(self.lineEdit1_1) self.right1_1.addWidget(self.slider1_1) self.right1_2.addWidget(self.label1_2) self.right1_2.addWidget(self.lineEdit1_2) self.right1_2.addWidget(self.slider1_2) self.right1.addLayout(self.right1_1) self.right1.addLayout(self.right1_2) self.right1.addWidget(self.button1_3) except Exception as err: print(err) # 对框架进行设置,并把框架添加到主框架中 try: self.stack.setStackingMode(1) self.main_box.addLayout(self.left1) self.main_box.addLayout(self.right1) self.setLayout(self.main_box) except Exception as err: print(err) # 设置色块label覆盖区域的初始数据,如边距和颜色 try: self.mask_left_margin = 78 self.mask_up_margin = 33 self.mask_right_margin = 65 self.mask_bottom_margin = 30 self.mask_range = self.F.width( ) - self.mask_left_margin - self.mask_right_margin self.mask.setStyleSheet( "QLabel{background-color: rgb(0,0,255,50);}") self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) except Exception as err: print("mask Error:{0}".format(err)) # 为设置的信号绑定函数 try: self.from_slider_min.connect(self.change_line_min_value) self.from_slider_min.connect(self.change_label_min) self.from_slider_max.connect(self.change_line_max_value) self.from_slider_max.connect(self.change_label_max) self.from_line_min.connect(self.change_sld_min_value) self.from_line_min.connect(self.change_label_min) self.from_line_max.connect(self.change_sld_max_value) self.from_line_max.connect(self.change_label_max) self.from_label_max.connect(self.change_line_max_value) self.from_label_max.connect(self.change_sld_max_value) self.from_label_min.connect(self.change_line_min_value) self.from_label_min.connect(self.change_sld_min_value) except Exception as err: print("Signal Error:{0}".format(err)) def histpath(self, path): self.img_path = path self.F.hist(self.img_path) # 分解拖动 = 按下 + 移动 # 按下事件,记录按下时的位置 def label_mouse_press_event(self, event): if event.buttons() == Qt.LeftButton: self.press = event.pos().x() # 移动事件,仅在左键被按下时进行处理,获取当前的位置,计算和初始位置的差值,把差值传递到移动label的函数 def label_mouse_move_event(self, event): if event.buttons() == Qt.LeftButton: self.now = event.pos().x() self.differ = self.now - self.press self.move_mask(self.differ) # 判定文本是否符合要求 def is_available_int(self, text): try: if int(text) >= 0 and int(text) <= 255: return True return False except: print("except") return False # 计算label色块的左边界 def eva_start(self): return int(round( self.minValue * self.mask_range / 255.0)) + self.mask_left_margin # 计算label色块的右边界 def eva_end(self): return int(round((255 - self.maxValue) * self.mask_range / 255.0)) + self.mask_right_margin # 拖动滑块触发的事件,区分是操作控件时触发还是其他控件的信号触发 def change_sld_min_value(self, value): # 其他信号触发 if type(self.sender()) != QSlider: # 屏蔽自身信号,避免死循环 self.slider1_1.blockSignals(True) try: self.slider1_1.setValue(value) except Exception as err: print("err in set slider1_1 value: {0}".format(err)) self.slider1_1.blockSignals(False) # 自身信号触发 else: if value > self.maxValue: QMessageBox.information( self, "Warning", "Minimum value cannot be greater than maximum value") value = 0 self.slider1_1.setValue(value) else: self.from_slider_min.emit(str(value)) # 拖动滑块触发的事件,区分是操作控件时触发还是其他控件的信号触发 def change_sld_max_value(self, value): # 其他信号触发 if type(self.sender()) != QSlider: # 屏蔽自身信号,避免死循环 self.slider1_2.blockSignals(True) try: self.slider1_2.setValue(value) except Exception as err: print("err in set slider1_2 value: {0}".format(err)) self.slider1_2.blockSignals(False) # 自身信号触发 else: if value < self.minValue: QMessageBox.information( self, "Warning", "Maximum value cannot be less than minimum value") value = 255 self.slider1_2.setValue(value) else: self.from_slider_max.emit(str(value)) # 更改文本(Min Val)触发的事件,区分是操作控件时触发还是其他控件的信号触发 def change_line_min_value(self, text): # 来自其他信号 if type(self.sender()) != QLineEdit: self.lineEdit1_1.blockSignals(True) self.lineEdit1_1.setText(str(text)) self.lineEdit1_1.blockSignals(False) else: # 来自自身事件信号 # 判定文本是否符合要求 if self.is_available_int(text): if int(text) > self.maxValue: QMessageBox.information( self, "Warning", "Minimum value cannot be greater than maximum value") text = "0" self.lineEdit1_1.setText(str(text)) else: self.from_line_min.emit(int(text)) else: QMessageBox.information( self, "Attention", "Please enter a positive integer between 0 and 255") self.lineEdit1_1.blockSignals(True) self.lineEdit1_1.setText("0") self.lineEdit1_1.blockSignals(False) def change_line_min_value_finished(self): text = self.lineEdit1_1.text() # 来自其他信号 if type(self.sender()) != QLineEdit: self.lineEdit1_1.blockSignals(True) self.lineEdit1_1.setText(str(text)) self.lineEdit1_1.blockSignals(False) else: # 来自自身事件信号 # 判定文本是否符合要求 if self.is_available_int(text): if int(text) > self.maxValue: QMessageBox.information( self, "Warning", "Minimum value cannot be greater than maximum value") text = "0" self.lineEdit1_1.setText(str(text)) else: self.from_line_min.emit(int(text)) else: QMessageBox.information( self, "Attention", "Please enter a positive integer between 0 and 255") self.lineEdit1_1.blockSignals(True) self.lineEdit1_1.setText("0") self.lineEdit1_1.blockSignals(False) def change_line_max_value(self, text): if type(self.sender()) != QLineEdit: # 其他信号触发 self.lineEdit1_2.blockSignals(True) self.lineEdit1_2.setText(str(text)) self.lineEdit1_2.blockSignals(False) else: # 来自自身事件信号 # 判定文本是否符合要求 if self.is_available_int(text): if int(text) < self.minValue: QMessageBox.information( self, "Warning", "Maximum value cannot be less than minimum value") text = "255" self.lineEdit1_2.setText(str(text)) else: self.from_line_max.emit(int(text)) else: QMessageBox.information( self, "Attention", "Please enter a positive integer between 0 and 255") self.lineEdit1_2.blockSignals(True) self.lineEdit1_2.setText("255") self.lineEdit1_2.blockSignals(False) def change_line_max_value_finished(self): text = self.lineEdit1_2.text() if type(self.sender()) != QLineEdit: # 其他信号触发 self.lineEdit1_2.blockSignals(True) self.lineEdit1_2.setText(str(text)) self.lineEdit1_2.blockSignals(False) else: # 来自自身事件信号 # 判定文本是否符合要求 if self.is_available_int(text): if int(text) < self.minValue: QMessageBox.information( self, "Warning", "Maximum value cannot be less than minimum value") text = "255" self.lineEdit1_2.setText(str(text)) else: self.from_line_max.emit(int(text)) else: QMessageBox.information( self, "Attention", "Please enter a positive integer between 0 and 255") self.lineEdit1_2.blockSignals(True) self.lineEdit1_2.setText("255") self.lineEdit1_2.blockSignals(False) # 只会从其他控件处收到信号,更改色块显示的范围 def change_label_min(self, left): if self.is_available_int(left): if int(left) <= self.maxValue: self.minValue = int(left) self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) # 只会从其他控件处收到信号,更改色块显示的范围 def change_label_max(self, right): if self.is_available_int(right): if int(right) >= self.minValue: self.maxValue = int(right) self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) # 拖动色块时触发,重新计算左右边界,并把信号传递到其他控件 def move_mask(self, diff): if self.is_available_int( self.minValue + diff) and self.is_available_int(self.maxValue + diff): self.minValue += diff self.maxValue += diff self.avail_range = self.maxValue - self.minValue self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) self.from_label_min.emit(self.minValue) self.from_label_max.emit(self.maxValue) elif self.is_available_int(self.minValue + diff): self.maxValue = 255 self.minValue = 255 - self.avail_range self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) self.from_label_min.emit(self.minValue) self.from_label_max.emit(self.maxValue) elif self.minValue + diff < 0: self.minValue = 0 self.maxValue = self.avail_range self.mask_layout.setContentsMargins(self.eva_start(), self.mask_up_margin, self.eva_end(), self.mask_bottom_margin) self.from_label_min.emit(self.minValue) self.from_label_max.emit(self.maxValue) # 按下按钮时触发,对图片进行处理,先处理大于Min的部分,然后处理小于Max的部分,然后取交集 def apply(self): try: if is_gray(self.img_path): self.img = skimage.io.imread(self.img_path) # ret, temp_image1 = cv2.threshold(self.img, self.minValue, 255, cv2.THRESH_BINARY) # ret, temp_image2 = cv2.threshold(self.img, self.maxValue, 255, cv2.THRESH_BINARY_INV) # image = cv2.bitwise_and(temp_image1, temp_image2) dt = np.zeros_like(self.img) dt[(self.img >= self.minValue) & (self.img <= self.maxValue)] = 1.0 # cv2.imshow("double threshold", dt * 255) # cv2.waitKey() # cv2.destroyAllWindows() dt *= 255 return dt except Exception as err: print("apply error:{}".format(err))
class gpvdm_contact_resistance(QWidget): changed = pyqtSignal() def __init__(self, file_box=True): QWidget.__init__(self) self.raw_value = "ground" self.hbox = QHBoxLayout() self.edit = QLineEdit() self.combobox = QComboBoxLang() self.combobox.addItemLang("ground", _("Ground")) self.combobox.addItemLang("constant", _("Constant bias")) self.combobox.addItemLang("change", _("Change")) self.hbox.addWidget(self.combobox) self.hbox.addWidget(self.edit) self.hbox.setContentsMargins(0, 0, 0, 0) self.edit.setStyleSheet("QLineEdit { border: none }") #self.button.clicked.connect(self.callback_button_click) self.combobox.currentIndexChanged.connect(self.callback_combobox) self.edit.textChanged.connect(self.callback_edit) self.setLayout(self.hbox) def update(self): self.edit.blockSignals(True) cb_value = self.applied_voltage_type if cb_value != "ground" and cb_value != "change": self.edit.setEnabled(True) self.edit.setText(self.applied_voltage) else: self.edit.setEnabled(False) if cb_value == "ground": self.edit.setText("0") elif cb_value == "change": self.edit.setText("Vsig") self.edit.blockSignals(False) def callback_edit(self): try: self.applied_voltage = str(float(self.edit.text())) self.changed.emit() except: pass def callback_combobox(self): self.applied_voltage_type = self.combobox.currentText_english() self.update() self.changed.emit() def setText(self, text): self.applied_voltage_type = text.split(":")[0] self.applied_voltage = text.split(":")[1] self.combobox.setValue_using_english(self.applied_voltage_type) self.update() def text(self): return self.applied_voltage_type + ":" + self.applied_voltage
class CColorInfos(QWidget): colorChanged = pyqtSignal(QColor, int) colorAdded = pyqtSignal(QColor) def __init__(self, *args, **kwargs): super(CColorInfos, self).__init__(*args, **kwargs) layout = QGridLayout(self) layout.setContentsMargins(11, 2, 11, 2) layout.setSpacing(8) self.editHex = QLineEdit('#FF0000', self, alignment=Qt.AlignCenter, objectName='editHex', textChanged=self.onHexChanged) self.editHex.setValidator( QRegExpValidator(QRegExp('#[0-9a-fA-F]{6}$'), self.editHex)) self.labelHex = QLabel('HEX', self, alignment=Qt.AlignCenter) layout.addWidget(self.editHex, 0, 0) layout.addWidget(self.labelHex, 1, 0) layout.addItem( QSpacerItem(10, 20, QSizePolicy.Fixed, QSizePolicy.Minimum), 0, 1) self.editRed = QSpinBox(self, buttonSymbols=QSpinBox.NoButtons, alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged) self.editRed.setRange(0, 255) self.labelRed = QLabel('R', self, alignment=Qt.AlignCenter) layout.addWidget(self.editRed, 0, 2) layout.addWidget(self.labelRed, 1, 2) self.editGreen = QSpinBox(self, buttonSymbols=QSpinBox.NoButtons, alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged) self.editGreen.setRange(0, 255) self.labelGreen = QLabel('G', self, alignment=Qt.AlignCenter) layout.addWidget(self.editGreen, 0, 3) layout.addWidget(self.labelGreen, 1, 3) self.editBlue = QSpinBox(self, buttonSymbols=QSpinBox.NoButtons, alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged) self.editBlue.setRange(0, 255) self.labelBlue = QLabel('B', self, alignment=Qt.AlignCenter) layout.addWidget(self.editBlue, 0, 4) layout.addWidget(self.labelBlue, 1, 4) self.editAlpha = QSpinBox(self, buttonSymbols=QSpinBox.NoButtons, alignment=Qt.AlignCenter, valueChanged=self.onRgbaChanged) self.editAlpha.setRange(0, 255) self.labelAlpha = QLabel('A', self, alignment=Qt.AlignCenter) layout.addWidget(self.editAlpha, 0, 5) layout.addWidget(self.labelAlpha, 1, 5) layout.addItem( QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum), 0, 6) layout.addWidget( QPushButton('+', self, cursor=Qt.PointingHandCursor, toolTip='添加自定义颜色', clicked=self.onColorAdd), 0, 7) layout.setColumnStretch(0, 3) layout.setColumnStretch(1, 1) layout.setColumnStretch(2, 1) layout.setColumnStretch(3, 1) layout.setColumnStretch(4, 1) layout.setColumnStretch(5, 1) layout.setColumnStretch(6, 2) layout.setColumnStretch(7, 1) self.setFocus() self.editRed.setValue(255) self.editAlpha.setValue(255) def reset(self): pass def onColorAdd(self): self.colorAdded.emit( QColor(self.editRed.value(), self.editGreen.value(), self.editBlue.value())) def setHex(self, code): self.editHex.setText(str(code)) def updateColor(self, color): self.editRed.setValue(color.red()) self.editGreen.setValue(color.green()) self.editBlue.setValue(color.blue()) def updateAlpha(self, _, alpha): self.editAlpha.setValue(alpha) def onHexChanged(self, code): if len(code) != 7: return color = QColor(code) if color.isValid(): self.blockRgbaSignals(True) self.editHex.blockSignals(True) self.editRed.setValue(color.red()) self.editGreen.setValue(color.green()) self.editBlue.setValue(color.blue()) self.editAlpha.setValue(color.alpha()) self.colorChanged.emit(color, color.alpha()) self.editHex.blockSignals(False) self.blockRgbaSignals(False) def onRgbaChanged(self, _): self.editHex.blockSignals(True) self.blockRgbaSignals(True) color = QColor(self.editRed.value(), self.editGreen.value(), self.editBlue.value(), self.editAlpha.value()) self.editHex.setText(color.name()) self.colorChanged.emit(color, self.editAlpha.value()) self.blockRgbaSignals(False) self.editHex.blockSignals(False) def blockRgbaSignals(self, block=True): self.editRed.blockSignals(block) self.editGreen.blockSignals(block) self.editBlue.blockSignals(block) self.editAlpha.blockSignals(block) def sizeHint(self): return QSize(280, 48)
class ConfigROIWindow(QWidget): def __init__(self): super().__init__() self.setGeometry(720, 140, 100, 100) self.setWindowTitle("Configure ROI") self.layout = QGridLayout() self.roiDict = {} self.home() def home(self): self.roiNum = QSpinBox(self) #no. of ROI self.roiNum.setValue(1) self.roiNum.setSingleStep(1) self.roiNum.setRange(1, 100) self.roiNum.valueChanged.connect(self.num_change) self.label1 = QLabel("ROI Number", self) self.roiLabel = QLineEdit(self) #Name of ROI ## self.roiLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.roiLabel.setText("") self.roiLabel.textChanged.connect(self.label_change) self.label2 = QLabel("ROI Name", self) self.roiDrawBtn = QPushButton("Draw ROI", self) #Draw ROI self.roiDrawBtn.setEnabled(False) ## self.roiDrawBtn.clicked.connect(self.roi_draw) self.roiDef = QLabel("ROI Definition:", self) #display definitions self.delBtn = QPushButton("Delete", self) #Delete current definition self.delBtn.clicked.connect(self.del_roi) self.okBtn = QPushButton("OK", self) #Close window self.layout.addWidget(self.roiNum, 1, 0, 1, 1) self.layout.addWidget(self.label1, 0, 0, 1, 1) self.layout.addWidget(self.roiLabel, 1, 1, 1, 1) self.layout.addWidget(self.label2, 0, 1, 1, 1) self.layout.addWidget(self.roiDrawBtn, 2, 0, 1, 1) self.layout.addWidget(self.delBtn, 2, 1, 1, 1) self.layout.addWidget(self.roiDef, 0, 2, 3, 1) self.layout.addWidget(self.okBtn, 3, 0, 1, 2) self.setLayout(self.layout) def num_change(self): key = self.roiNum.value() self.roiLabel.blockSignals(True) if key in self.roiDict.keys(): self.roiLabel.setText(self.roiDict[key]) self.roiDrawBtn.setEnabled(True) else: self.roiLabel.setText("") self.roiDrawBtn.setEnabled(False) self.roiLabel.blockSignals(False) def label_change(self): if self.roiLabel.text() in ["Dict", "dict", "All", "all"]: #banned keywords self.roiLabel.blockSignals(True) self.roiLabel.setText("") self.roiLabel.blockSignals(False) self.roiDict[self.roiNum.value()] = self.roiLabel.text() self.update_def() self.roiDrawBtn.setEnabled(False) else: self.roiDict[self.roiNum.value()] = self.roiLabel.text() self.update_def() self.roiDrawBtn.setEnabled(True) def del_roi(self): #delete current definition del self.roiDict[self.roiNum.value()] self.roiNum.setValue(self.roiNum.value() - 1) self.update_def() def update_def(self): #update definition label defString = "ROI Definition:" for k in self.roiDict.keys(): defString += "\nROI " + str(k) + ":\t" + self.roiDict[k] self.roiDef.setText(defString) def roi_draw(self): pass def showWindow(self): self.show()
def updateView(self, value): widget = self QLineEdit.blockSignals(widget, True) QLineEdit.setText(widget, str(value)) QLineEdit.blockSignals(widget, False)
class ColourPallet2(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.col = QColor(0, 84, 154) red = QLabel('Red') green = QLabel('Green') blue = QLabel('Blue') hue = QLabel('Hue') saturation = QLabel('Saturation') luminace = QLabel('Luminace') self.redSlider = QSlider() self.redSlider.setSingleStep(1) self.redSlider.setMaximum(255) self.redSlider.valueChanged.connect(self.rgb1SlidersChanged) self.greenSlider = QSlider() self.greenSlider.setSingleStep(1) self.greenSlider.setMaximum(255) self.greenSlider.valueChanged.connect(self.rgb1SlidersChanged) self.blueSlider = QSlider() self.blueSlider.setSingleStep(1) self.blueSlider.setMaximum(255) self.blueSlider.valueChanged.connect(self.rgb1SlidersChanged) self.hueSlider = QSlider() self.hueSlider.setSingleStep(1) self.hueSlider.setMaximum(359) self.hueSlider.valueChanged.connect(self.hslSliderChanged) self.saturationSlider = QSlider() self.saturationSlider.setSingleStep(1) self.saturationSlider.setMaximum(255) self.saturationSlider.valueChanged.connect(self.hslSliderChanged) self.luminaceSlider = QSlider() self.luminaceSlider.setSingleStep(1) self.luminaceSlider.setMaximum(255) self.luminaceSlider.valueChanged.connect(self.hslSliderChanged) grid1 = QGridLayout() grid1.setHorizontalSpacing(10) grid1.setContentsMargins(0, 0, 0, 0) for i in range(6): grid1.setColumnStretch(i, 1) grid1.setColumnMinimumWidth(i, 50) grid1.setRowMinimumHeight(i, 10) grid1.addWidget(red, 0, 0, alignment=Qt.AlignCenter) grid1.addWidget(self.redSlider, 1, 0, alignment=Qt.AlignCenter) grid1.addWidget(green, 0, 1, alignment=Qt.AlignCenter) grid1.addWidget(self.greenSlider, 1, 1, alignment=Qt.AlignCenter) grid1.addWidget(blue, 0, 2, alignment=Qt.AlignCenter) grid1.addWidget(self.blueSlider, 1, 2, alignment=Qt.AlignCenter) grid1.addWidget(hue, 0, 3, alignment=Qt.AlignCenter) grid1.addWidget(self.hueSlider, 1, 3, alignment=Qt.AlignCenter) grid1.addWidget(saturation, 0, 4, alignment=Qt.AlignCenter) grid1.addWidget(self.saturationSlider, 1, 4, alignment=Qt.AlignCenter) grid1.addWidget(luminace, 0, 5, alignment=Qt.AlignCenter) grid1.addWidget(self.luminaceSlider, 1, 5, alignment=Qt.AlignCenter) rgb1_ = QLabel('rgb') rgb255_ = QLabel('RGB') hex_ = QLabel('Hex') hsl_ = QLabel('hsl') self.rgb1Edit = QLineEdit() self.rgb1Edit.editingFinished.connect(self.rg1EditChanged) self.rgb255Edit = QLineEdit() self.rgb255Edit.editingFinished.connect(self.rgb255EditChanged) self.hexEdit = QLineEdit() self.hexEdit.editingFinished.connect(self.hexEditChanged) self.hslEdit = QLineEdit() self.hslEdit.editingFinished.connect(self.hslEditChanged) grid2 = QGridLayout() grid2.setVerticalSpacing(2) grid2.setHorizontalSpacing(20) grid2.setContentsMargins(0, 0, 0, 0) grid2.addWidget(rgb1_, 0, 0) grid2.addWidget(rgb255_, 0, 1) grid2.addWidget(hex_, 0, 2) grid2.addWidget(hsl_, 0, 3) grid2.addWidget(self.rgb1Edit, 1, 0) grid2.addWidget(self.rgb255Edit, 1, 1) grid2.addWidget(self.hexEdit, 1, 2) grid2.addWidget(self.hslEdit, 1, 3) self.square = QFrame(self) self.square.setStyleSheet("QWidget { background-color: %s }" % self.col.name()) grid = QGridLayout() grid.addLayout(grid1, 0, 0) grid.addLayout(grid2, 1, 0) grid.addWidget(self.square, 2, 0) for i in (0, 2): grid.setRowStretch(i, 1) grid.setVerticalSpacing(10) self.setLayout(grid) self.setEverything() self.setGeometry(400, 400, 500, 400) self.setWindowTitle('Colour Pallet 2') self.show() def rg1EditChanged(self): rgb1 = ast.literal_eval(self.rgb1Edit.text()) rgb255 = [round(i * 255) for i in rgb1] self.col.setRgb(*rgb255) self.setEverything() def rgb255EditChanged(self): rgb255 = ast.literal_eval(self.rgb255Edit.text()) self.col.setRgb(*rgb255) self.setEverything() def hexEditChanged(self): self.col.setNamedColor(self.hexEdit.text()) self.setEverything() def hslEditChanged(self): hsl = ast.literal_eval(self.hslEdit.text()) self.col.setHsl(*hsl) self.setEverything() def rgb1SlidersChanged(self): self.col.setRgb(self.redSlider.value(), self.greenSlider.value(), self.blueSlider.value()) self.setEverything() def hslSliderChanged(self): self.col.setHsl(self.hueSlider.value(), self.saturationSlider.value(), self.luminaceSlider.value()) self.setEverything() def setEverything(self): hsl = self.col.getHsl()[0:3] Hex = self.col.name() rgb255 = self.col.getRgb()[0:3] rgb1 = [i / 255 for i in rgb255][0:3] self.BlockSignals(True) self.hslEdit.setText(str(hsl)) self.hexEdit.setText(Hex) self.rgb255Edit.setText(str(rgb255)) self.rgb1Edit.setText(str(rgb1)) self.redSlider.setValue(rgb255[0]) self.greenSlider.setValue(rgb255[1]) self.blueSlider.setValue(rgb255[2]) self.hueSlider.setValue(hsl[0]) self.saturationSlider.setValue(hsl[1]) self.luminaceSlider.setValue(hsl[2]) self.square.setStyleSheet("QWidget { background-color: %s }" % self.col.name()) self.BlockSignals(False) def BlockSignals(self, state): self.hslEdit.blockSignals(state) self.hexEdit.blockSignals(state) self.rgb255Edit.blockSignals(state) self.rgb1Edit.blockSignals(state) self.redSlider.blockSignals(state) self.greenSlider.blockSignals(state) self.blueSlider.blockSignals(state) self.hueSlider.blockSignals(state) self.saturationSlider.blockSignals(state) self.luminaceSlider.blockSignals(state)
class CaesarCipherDialog(QDialog): def __init__(self, input: str, config: PluginConfig, codec): super(CaesarCipherDialog, self).__init__() self.config = config main_layout = QVBoxLayout() main_layout.addWidget(self._init_editor_frame()) main_layout.addWidget(self._init_button_box()) self.setLayout(main_layout) self.setWindowIcon(qtawesome.icon("fa.edit")) self.setWindowTitle("Caesar Cipher") self._setup_shortcuts() self._input = input self._text_area.setPlainText(self._input) self._codec = codec ############################################# # Initialize ############################################# def _setup_shortcuts(self): ctrl_return_shortcut = QShortcut(QKeySequence(Qt.CTRL + Qt.Key_Return), self) ctrl_return_shortcut.activated.connect(self._accept) alt_return_shortcut = QShortcut(QKeySequence(Qt.ALT + Qt.Key_Return), self) alt_return_shortcut.activated.connect(self._accept) alt_o_shortcut = QShortcut(QKeySequence(Qt.ALT + Qt.Key_O), self) alt_o_shortcut.activated.connect(self._accept) def _init_button_box(self): button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self._accept) button_box.rejected.connect(self.reject) return button_box def _init_editor_frame(self): main_frame = QFrame() main_frame_layout = QVBoxLayout() slider_frame = self._init_slider_frame() main_frame_layout.addWidget(slider_frame) self._text_area = QPlainTextEdit() self._text_area.setReadOnly(True) self._text_area.setFixedHeight(126) main_frame_layout.addWidget(self._text_area) main_frame.setLayout(main_frame_layout) return main_frame def _init_slider_frame(self): slider_frame = QFrame() slider_frame_layout = QHBoxLayout() self._shift_slider = QSlider(Qt.Horizontal) self._shift_slider.setMinimum(0) self._shift_slider.setMaximum(26) self._shift_slider.setValue(self.config.get(Plugin.Option.Shift).value) self._shift_slider.valueChanged.connect(self._shift_slider_changed) slider_frame_layout.addWidget(self._shift_slider) self._shift_text = QLineEdit() self._shift_text.setText( str(self.config.get(Plugin.Option.Shift).value)) self._shift_text.setFixedWidth(30) self._shift_text.setValidator(QIntValidator(0, 26)) self._shift_text.textChanged.connect(self._shift_text_changed) slider_frame_layout.addWidget(self._shift_text) self._shift_calculate_button = QPushButton("Calculate") self._shift_calculate_button.clicked.connect( self._shift_calculate_button_clicked) slider_frame_layout.addWidget(self._shift_calculate_button) slider_frame.setLayout(slider_frame_layout) return slider_frame ############################################# # Events ############################################# def _shift_calculate_button_clicked(self): offset = self._codec.calculate_offset(self._input) self._shift_slider.setSliderPosition(offset) def _shift_slider_changed(self, shift): if not shift: shift = 0 self._shift_changed(shift) def _shift_text_changed(self, shift): if not shift: shift = 0 self._shift_changed(int(shift)) def _shift_changed(self, shift): self._shift_text.blockSignals(True) self._shift_slider.blockSignals(True) self._shift_slider.setValue(shift) self._shift_text.setText(str(shift)) self._text_area.setPlainText(self._codec.run(self._input, shift)) self._shift_slider.blockSignals(False) self._shift_text.blockSignals(False) ############################################# # Private interface ############################################# def _get_shift(self): return self._shift_slider.value() def _accept(self): self.config.update({Plugin.Option.Shift.key: self._get_shift()}) self.accept() ############################################# # Public interface ############################################# def setInput(self, input) -> str: self._input = input self._shift_changed(self._get_shift())
class SendFundsDestinationItem(QObject): sig_remove_address = QtCore.pyqtSignal(object) sig_use_all_funds = QtCore.pyqtSignal(object) sig_amount_changed = QtCore.pyqtSignal(object) def __init__(self, parent, app_config, grid_layout, row_index, address_widget_width): QObject.__init__(self) self.app_config = app_config self.main_layout = grid_layout self.row_number = row_index self.values_unit = OUTPUT_VALUE_UNIT_AMOUNT self.value_amount = None self.value_percent = None self.inputs_total_amount = None # sum of all inputs (for counting percent type value) self.address_widget_width = address_widget_width self.setupUi(parent) def setupUi(self, Form): self.lbl_dest_address = QLabel(Form) self.lbl_dest_address.setText("Address") self.lbl_dest_address.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) self.main_layout.addWidget(self.lbl_dest_address, self.row_number, 0) self.edt_dest_address = QLineEdit(Form) self.edt_dest_address.setFixedWidth(self.address_widget_width) self.main_layout.addWidget(self.edt_dest_address, self.row_number, 1) self.lbl_amount = QLabel(Form) self.lbl_amount.setText("Amount") self.main_layout.addWidget(self.lbl_amount, self.row_number, 2) self.lay_amount = QHBoxLayout() self.lay_amount.setContentsMargins(0, 0, 0, 0) self.lay_amount.setSpacing(0) self.main_layout.addLayout(self.lay_amount, self.row_number, 3) self.edt_amount = QLineEdit(Form) self.edt_amount.setFixedWidth(100) self.edt_amount.textChanged.connect(self.on_edt_amount_changed) self.lay_amount.addWidget(self.edt_amount) self.btn_use_all = QToolButton(Form) self.btn_use_all.setText('\u2b06') self.btn_use_all.setFixedSize(14, self.edt_amount.sizeHint().height()) self.btn_use_all.setToolTip('Use remaining funds') self.btn_use_all.clicked.connect(self.on_btn_use_all_funds_clicked) self.lay_amount.addWidget(self.btn_use_all) # label for the second unit (e.g. percent if self.values_unit equals OUTPUT_VALUE_UNIT_AMOUNT) self.lbl_second_unit_value = QLabel(Form) self.main_layout.addWidget(self.lbl_second_unit_value, self.row_number, 4) self.btn_remove_address = QToolButton(Form) self.btn_remove_address.setFixedSize( self.edt_amount.sizeHint().height(), self.edt_amount.sizeHint().height()) self.main_layout.addWidget(self.btn_remove_address, self.row_number, 5) self.btn_remove_address.setStyleSheet("QToolButton{color: red}") self.btn_remove_address.setVisible(False) self.btn_remove_address.clicked.connect( self.on_btn_remove_address_clicked) self.btn_remove_address.setText('\u2716') # 2501, 2716 self.btn_remove_address.setToolTip("Remove address") def set_btn_remove_address_visible(self, visible): self.btn_remove_address.setVisible(visible) def on_btn_remove_address_clicked(self): self.sig_remove_address.emit(self) def on_btn_use_all_funds_clicked(self): self.sig_use_all_funds.emit(self) def on_edt_amount_changed(self, text): try: value = round(float(self.edt_amount.text()), 8) if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: self.value_amount = value elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: self.value_percent = value self.re_calculate_second_unit_value() self.sig_amount_changed.emit(self) except Exception: pass def get_value(self, default_value=None): """ :param default_value: value that will be returned if the value entered by a user is invalid or empty """ amount = self.edt_amount.text() if amount: try: return float(amount) except Exception: pass return default_value def get_value_amount(self): return self.value_amount def set_value(self, value): old_state = self.edt_amount.blockSignals(True) try: if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: self.value_amount = value elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: self.value_percent = value self.re_calculate_second_unit_value() self.edt_amount.setText(app_utils.to_string(value)) finally: self.edt_amount.blockSignals(old_state) self.edt_amount.update() def display_second_unit_value(self): if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: if self.value_percent is not None: self.lbl_second_unit_value.setText( app_utils.to_string(round(self.value_percent, 3)) + '%') else: self.lbl_second_unit_value.setText('') elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: if self.value_amount is not None: self.lbl_second_unit_value.setText( app_utils.to_string(round(self.value_amount, 8)) + ' Dash') else: self.lbl_second_unit_value.setText('') def re_calculate_second_unit_value(self): if self.values_unit == OUTPUT_VALUE_UNIT_AMOUNT: # calculate the percent-value based on the inputs total amount and our item's amount if self.inputs_total_amount and self.value_amount is not None: self.value_percent = round( self.value_amount * 100 / self.inputs_total_amount, 8) self.display_second_unit_value() elif self.values_unit == OUTPUT_VALUE_UNIT_PERCENT: # calculate the amount value based on inputs total amount and our item's percent-value if self.inputs_total_amount is not None and self.value_percent is not None: self.value_amount = round( self.inputs_total_amount * self.value_percent / 100, 8) self.display_second_unit_value() def set_inputs_total_amount(self, amount): self.inputs_total_amount = amount self.re_calculate_second_unit_value() def set_address(self, address): self.edt_dest_address.setText(address) def get_address(self): return self.edt_dest_address.text() def set_output_value_unit(self, unit): old_state = self.edt_amount.blockSignals(True) try: if unit == OUTPUT_VALUE_UNIT_AMOUNT: self.edt_amount.setText(app_utils.to_string(self.value_amount)) self.lbl_amount.setText('value') elif unit == OUTPUT_VALUE_UNIT_PERCENT: self.edt_amount.setText(app_utils.to_string( self.value_percent)) self.lbl_amount.setText('pct. value') else: raise Exception('Invalid unit') self.values_unit = unit self.display_second_unit_value() finally: self.edt_amount.blockSignals(old_state) self.edt_amount.update() def set_style_sheet(self): style = 'QLineEdit[invalid="true"]{border: 1px solid red}' self.edt_dest_address.setStyleSheet(style) self.edt_amount.setStyleSheet(style) def validate(self): valid = True address = self.edt_dest_address.text() if not address: valid = False elif not dash_utils.validate_address(address, self.app_config.dash_network): valid = False else: self.message = None if valid: self.edt_dest_address.setProperty('invalid', False) else: self.edt_dest_address.setProperty('invalid', True) amount = self.edt_amount.text() try: amount = float(amount) if amount > 0.0: self.edt_amount.setProperty('invalid', False) else: self.edt_amount.setProperty('invalid', True) valid = False except: self.edt_amount.setProperty('invalid', True) valid = False self.set_style_sheet() return valid def clear_validation_results(self): self.edt_amount.setProperty('invalid', False) self.edt_dest_address.setProperty('invalid', False) self.set_style_sheet()
class SpellCheckDialog(QDialog): """Dialog to perform and control the spell check operation. """ misspellFound = pyqtSignal() changeRequest = pyqtSignal(str) def __init__(self, spellCheckInterface, parent=None): """Create the dialog. Arguments: spellCheckInterface -- a reference to the spell engine interface parent -- the parent dialog """ super().__init__(parent) self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint) self.setWindowTitle(_('Spell Check')) self.spellCheckInterface = spellCheckInterface self.textLineIter = None self.textLine = '' self.replaceAllDict = {} self.tmpIgnoreWords = set() self.word = '' self.postion = 0 topLayout = QHBoxLayout(self) leftLayout = QVBoxLayout() topLayout.addLayout(leftLayout) wordBox = QGroupBox(_('Not in Dictionary')) leftLayout.addWidget(wordBox) wordLayout = QVBoxLayout(wordBox) label = QLabel(_('Word:')) wordLayout.addWidget(label) self.wordEdit = QLineEdit() wordLayout.addWidget(self.wordEdit) self.wordEdit.textChanged.connect(self.updateFromWord) wordLayout.addSpacing(5) label = QLabel(_('Context:')) wordLayout.addWidget(label) self.contextEdit = SpellContextEdit() wordLayout.addWidget(self.contextEdit) self.contextEdit.textChanged.connect(self.updateFromContext) suggestBox = QGroupBox(_('Suggestions')) leftLayout.addWidget(suggestBox) suggestLayout = QVBoxLayout(suggestBox) self.suggestList = QListWidget() suggestLayout.addWidget(self.suggestList) self.suggestList.itemDoubleClicked.connect(self.replace) rightLayout = QVBoxLayout() topLayout.addLayout(rightLayout) ignoreButton = QPushButton(_('Ignor&e')) rightLayout.addWidget(ignoreButton) ignoreButton.clicked.connect(self.ignore) ignoreAllButton = QPushButton(_('&Ignore All')) rightLayout.addWidget(ignoreAllButton) ignoreAllButton.clicked.connect(self.ignoreAll) rightLayout.addStretch() addButton = QPushButton(_('&Add')) rightLayout.addWidget(addButton) addButton.clicked.connect(self.add) addLowerButton = QPushButton(_('Add &Lowercase')) rightLayout.addWidget(addLowerButton) addLowerButton.clicked.connect(self.addLower) rightLayout.addStretch() replaceButton = QPushButton(_('&Replace')) rightLayout.addWidget(replaceButton) replaceButton.clicked.connect(self.replace) self.replaceAllButton = QPushButton(_('Re&place All')) rightLayout.addWidget(self.replaceAllButton) self.replaceAllButton.clicked.connect(self.replaceAll) rightLayout.addStretch() cancelButton = QPushButton(_('&Cancel')) rightLayout.addWidget(cancelButton) cancelButton.clicked.connect(self.reject) self.widgetDisableList = [ ignoreButton, ignoreAllButton, addButton, addLowerButton, self.suggestList ] self.fullDisableList = (self.widgetDisableList + [self.replaceAllButton, self.wordEdit]) def startSpellCheck(self, textLineIter): """Spell check text lines given in the iterator. Block execution except for the dialog if mispellings are found. Return True if spell check completes, False if cancelled. Arguments: textLineIter -- an iterator of text lines to check """ self.textLineIter = textLineIter try: self.textLine = next(self.textLineIter) except StopIteration: return True if self.spellCheck(): if self.exec_() == QDialog.Rejected: return False return True def continueSpellCheck(self): """Check lines, starting with current line. Exit the dialog if there are no more lines to check. """ if not self.spellCheck(): self.accept() def spellCheck(self): """Step through the iterator and spell check the lines. If results found, update the dialog with the results and return True. Return false if the end of the iterator is reached. """ while True: results = self.spellCheckInterface.checkLine( self.textLine, self.tmpIgnoreWords) if results: self.word, self.position, suggestions = results[0] newWord = self.replaceAllDict.get(self.word, '') if newWord: self.textLine = self.replaceWord(newWord) self.changeRequest.emit(self.textLine) else: self.misspellFound.emit() self.setWord(suggestions) return True try: self.textLine = next(self.textLineIter) self.tmpIgnoreWords.clear() except StopIteration: return False def setWord(self, suggestions): """Set dialog contents from the checked line and spell check results. Arguments: suggestions -- a list of suggested replacement words """ self.wordEdit.blockSignals(True) self.wordEdit.setText(self.word) self.wordEdit.blockSignals(False) self.contextEdit.blockSignals(True) self.contextEdit.setPlainText(self.textLine) self.contextEdit.setSelection(self.position, self.position + len(self.word)) self.contextEdit.blockSignals(False) self.suggestList.clear() self.suggestList.addItems(suggestions) self.suggestList.setCurrentItem(self.suggestList.item(0)) for widget in self.fullDisableList: widget.setEnabled(True) def replaceWord(self, newWord): """Return textLine with word replaced with newWord. Arguments: newWord -- the replacement word """ return (self.textLine[:self.position] + newWord + self.textLine[self.position + len(self.word):]) def ignore(self): """Set word to ignored (this check only) and continue spell check. """ self.tmpIgnoreWords.add(self.word) self.continueSpellCheck() def ignoreAll(self): """Add to dictionary's ignore list and continue spell check. """ self.spellCheckInterface.acceptWord(self.word) self.continueSpellCheck() def add(self): """Add misspelling to dictionary and continue spell check""" self.spellCheckInterface.addToDict(self.word, False) self.continueSpellCheck() def addLower(self): """Add misspelling to dictionary as lowercase and continue spell check. """ self.spellCheckInterface.addToDict(self.word, True) self.continueSpellCheck() def replace(self): """Replace misspelled word with suggestion or context edit box Then continue spell check. """ if self.suggestList.isEnabled(): newWord = self.suggestList.currentItem().text() self.textLine = self.replaceWord(newWord) else: self.textLine = self.contextEdit.toPlainText() self.changeRequest.emit(self.textLine) self.continueSpellCheck() def replaceAll(self): """Replace misspelled word with suggestion or word edit (in future too). Stores changed word in replaceAllDict and continues spell check. """ if self.suggestList.isEnabled(): newWord = self.suggestList.currentItem().text() else: newWord = self.wordEdit.text() self.textLine = self.replaceWord(newWord) self.replaceAllDict[self.word] = newWord self.changeRequest.emit(self.textLine) self.continueSpellCheck() def updateFromWord(self): """Update dialog after word line editor change. Disables suggests and ignore/add controls. Updates the context editor. """ for widget in self.widgetDisableList: widget.setEnabled(False) newWord = self.wordEdit.text() self.suggestList.clearSelection() self.contextEdit.blockSignals(True) self.contextEdit.setPlainText(self.replaceWord(newWord)) self.contextEdit.setSelection(self.position, self.position + len(newWord)) self.contextEdit.blockSignals(False) def updateFromContext(self): """Update dialog after context editor change. Disables controls except for replace. """ for widget in self.fullDisableList: widget.setEnabled(False) self.suggestList.clearSelection()
class FitDataWindow(QWidget): def __init__(self, *args, **kwargs): ## super(QWidget, self).__init__(*args, **kwargs) super().__init__() self.setGeometry(100, 100, 1000, 500) self.setWindowTitle("Data Fitting") self.home() def home(self): # startFitLabel = QLabel("Start (%):") # endFitLabel = QLabel("End (%):") # self.fitStart = QDoubleSpinBox(self) #fitting range start # self.fitStart.setValue(0) # self.fitStart.setSingleStep(1) # self.fitStart.setRange(0, 100) # self.fitStop = QDoubleSpinBox(self) #fitting range end # self.fitStop.setValue(100) # self.fitStop.setSingleStep(1) # self.fitStop.setRange(0, 100) params_list = ["Index", "Time", "Vertical piezo", "Lateral piezo", "Deformation", "Vertical force", "Lateral force"] xPlotLabel = QLabel("X Axis:", self) self.xPlot = QComboBox(self) #x param self.xPlot.addItems(params_list) self.xPlot.setCurrentIndex(0) self.xPlot.currentIndexChanged.connect(self.plotSequence) self.enableFitting = QCheckBox("Enable", self) self.enableFitting.stateChanged.connect(lambda: self.fitData(True)) xFitLabel = QLabel("X Parameter:", self) yFitLabel = QLabel("Y Parameter:", self) self.xFit = QComboBox(self) #x param self.xFit.addItems(params_list) self.xFit.setCurrentIndex(4) self.xFit.currentIndexChanged.connect(self.plotSequence) self.yFit = QComboBox(self) #x param self.yFit.addItems(params_list) self.yFit.setCurrentIndex(5) self.yFit.currentIndexChanged.connect(self.plotSequence) # self.xDict = {'Vertical piezo':None, # 'Lateral piezo':None, # 'Deformation':None, # 'Time':None, # 'Index':None} # self.yDict = {'Vertical force':None, # 'Lateral force':None} self.fileDataDict = {} fitparamLabel = QLabel("Fit Parameters:", self) self.fittingParams = QLineEdit(self) self.fittingParams.setText('m,c') self.fittingParams.textChanged.connect(self.updateTEX) self.params_old = self.fittingParams.text().split(',') guessValLabel = QLabel("Initial Guess:", self) self.guessValues = QLineEdit(self) self.guessValues.setText('0,0') lowBoundLabel = QLabel("Lower Bouond:", self) self.lowBound = QLineEdit(self) self.lowBound.setText(',') upBoundLabel = QLabel("Upper Bouond:", self) self.upBound = QLineEdit(self) self.upBound.setText(',') constantsLabel = QLabel("Constants:", self) self.constantParams = QLineEdit(self) self.constantParams.setText('p=1,q=2,r=3') self.constantParams.textChanged.connect(self.updateTEX) self.constants_old = [_x.split('=')[0] for _x in self.constantParams.text().split(',')] fitfunctionLabel = QLabel("Fitting Function:", self) self.fittingFunctionType = QComboBox(self) self.fittingFunctionType.addItem("Linear") self.fittingFunctionType.addItem("Quadratic") self.fittingFunctionType.addItem("Custom") self.fittingFunctionType.currentIndexChanged.connect(self.updateFitFunction) #standard functions: equation, params, guess, l_bound, u_bound self.functionDict = {'Linear': ['m*x+c', 'm,c', '0,0', ',', ','], 'Quadratic': ['a*x**2+b*x+c', 'a,b,c', '0,0,0', ',,', ',,'], 'Custom': ['a*x', 'a', '0', '', '']} self.fittingFunctionText = QTextEdit(self) self.fittingFunctionText.setText('m*x+c') self.fittingFunctionText.textChanged.connect(self.updateTEX) self.generateFunction() self.fittingFunctionTEX = MathTextLabel(self.mathText, self) self.applyFitBtn = QPushButton("Fit!", self) # self.applyFitBtn.clicked.connect(lambda: self.fitData(True)) self.fitResult = QTextEdit(self) self.fitResult.setText("Result:\n") self.fitResult.setReadOnly(True) # self.fitResult.setStyleSheet("QLabel { font-weight: bold; font-size: 16px;} ") self.plotInitialize() plot = PlotWidget(self.fig, cursor1_init = self.axes.get_xbound()[0], cursor2_init = self.axes.get_xbound()[1]) self.plotWidget = plot.wid # plotToolbar = NavigationToolbar(self.plotWidget, self) # self.fitPosLabel = QLabel("Fit Position\n(x,y):", self) #fit eq. position # self.fitPos = QLineEdit(self) # self.fitPos.setText('0.5,0.5') # self.showFitEq = QCheckBox('Show Slope', self) #display equation on plot paramGroupBox = QGroupBox() paramlayout=QGridLayout() paramGroupBox.setLayout(paramlayout) paramlayout.addWidget(xPlotLabel, 0, 0, 1, 1) paramlayout.addWidget(self.xPlot, 0, 1, 1, 1) paramlayout.addWidget(self.enableFitting, 0, 3, 1, 1) paramlayout.addWidget(xFitLabel, 1, 0, 1, 1) paramlayout.addWidget(self.xFit, 1, 1, 1, 1) paramlayout.addWidget(yFitLabel, 1, 2, 1, 1) paramlayout.addWidget(self.yFit, 1, 3, 1, 1) paramlayout.addWidget(fitparamLabel, 2, 0, 1, 1) paramlayout.addWidget(self.fittingParams, 2, 1, 1, 1) paramlayout.addWidget(guessValLabel, 2, 2, 1, 1) paramlayout.addWidget(self.guessValues, 2, 3, 1, 1) paramlayout.addWidget(lowBoundLabel, 3, 0, 1, 1) paramlayout.addWidget(self.lowBound, 3, 1, 1, 1) paramlayout.addWidget(upBoundLabel, 3, 2, 1, 1) paramlayout.addWidget(self.upBound, 3, 3, 1, 1) paramlayout.addWidget(constantsLabel, 4, 0, 1, 1) paramlayout.addWidget(self.constantParams, 4, 1, 1, 1) paramlayout.addWidget(fitfunctionLabel, 4, 2, 1, 1) paramlayout.addWidget(self.fittingFunctionType, 4, 3, 1, 1) paramlayout.addWidget(self.fittingFunctionText, 5, 0, 1, 4) paramlayout.addWidget(self.fittingFunctionTEX, 6, 0, 3, 4) paramlayout.addWidget(self.fitResult, 9,0, 1, 4) paramlayout.addWidget(self.applyFitBtn, 10, 1, 1, 2) # layout.addWidget(plotToolbar, 0, 4, 1, 6) # layout.addWidget(self.plotWidget, 1, 4, 9, 6) # plotGroupBox = QGroupBox() # plotlayout=QGridLayout() # plotGroupBox.setLayout(plotlayout) # plotlayout.addWidget(plot, 0, 0, 1, 1) # plotlayout.addWidget(plotToolbar, 0, 0, 1, 1) # plotlayout.addWidget(self.plotWidget, 1, 0, 1, 1) layout=QGridLayout() layout.addWidget(paramGroupBox, 0, 0, 1, 1) layout.addWidget(plot, 0, 1, 1, 1) self.setLayout(layout) # self.show() def updateFitFunction(self): logging.debug('test0') _key = self.fittingFunctionType.currentText() self.fittingFunctionText.blockSignals(True) self.fittingFunctionText.setText(self.functionDict[_key][0]) self.fittingFunctionText.blockSignals(False) logging.debug('test') self.fittingParams.blockSignals(True) self.fittingParams.setText(self.functionDict[_key][1]) self.fittingParams.blockSignals(False) logging.debug('test2') self.guessValues.setText(self.functionDict[_key][2]) self.lowBound.setText(self.functionDict[_key][3]) self.upBound.setText(self.functionDict[_key][4]) self.updateTEX() def updateTEX(self): #delete old variables for _x in self.params_old: if _x != '': exec('del ' + _x, globals()) for _x in self.constants_old: if _x != '': exec('del ' + _x, globals()) #update function self.generateFunction() self.params_old = self.fittingParams.text().split(',') self.constants_old = [_x.split('=')[0] for _x in self.constantParams.text().split(',')] #draw equation if self.mathText != None: self.fittingFunctionTEX.drawFigure(self.mathText) #below optional, remove later: CHECK! # self.plotRawData() # self.plotWidget.cursor1.set_ydata(self.axes.get_ybound()) #CHECK # self.plotWidget.cursor2.set_ydata(self.axes.get_ybound()) #CHECK ## self.plotWidget.add_cursors() # self.fitData(False) # self.updatePlot() #create fitting function and TEX format for display def generateFunction(self): try: math_functions = ['re','im','sign','Abs','arg','conjugate', 'polar_lift','periodic_argument', 'principal_branch','sin','cos','tan', 'cot','sec','csc','sinc','asin','acos', 'atan','acot','asec','acsc','atan2', 'sinh','cosh','tanh','coth','sech', 'csch','asinh','acosh','atanh','acoth', 'asech','acsch','ceiling','floor','frac', 'exp','LambertW','log','exp_polar','Min', 'Max','root','sqrt','cbrt','real_root','pi'] x_param = 'x' y_param = 'y' fit_params = self.fittingParams.text() constants = self.constantParams.text() # constant_vals = '1,2,3' equation_fit = self.fittingFunctionText.toPlainText() variables = x_param + ',' + fit_params global var var = sp.symbols(variables.replace(',',' ')) exec(variables + ' = var', globals()) for _x in constants.split(','): exec(_x, globals()) ## print(p+q) for item in math_functions: equation_fit = equation_fit.replace(item,'sp.'+item) self.mathText = r'$' + y_param + '=' + sp.latex(eval(equation_fit), ln_notation = True) + '$' self.func = sp.lambdify(list(var),eval(equation_fit)) logging.debug(self.mathText) except Exception as e: logging.error(str(e)) self.mathText = None def plotInitialize(self): self.fig = Figure(figsize=(5, 4), dpi=100) self.axes = self.fig.add_subplot(111) self.ax_raw = None # self.ax_norm = None self.ax_constr = None # #generate random data (for testing) xdata = np.linspace(0, 4, 50) self.fileDataDict[self.xFit.currentText()] = xdata self.fileDataDict[self.xPlot.currentText()] = xdata y = self.func(xdata, 2.5, 1.3) np.random.seed(1729) y_noise = 0.2 * np.random.normal(size=xdata.size) self.fileDataDict[self.yFit.currentText()] = y + y_noise self.plotRawData() self.updatePlot() self.fit_range = [None,None] def plotRawData(self): self.plotxdata= self.fileDataDict[self.xPlot.currentText()] self.xdata = self.fileDataDict[self.xFit.currentText()] self.ydata = self.fileDataDict[self.yFit.currentText()] if self.ax_raw != None: #check self.axes.lines.remove(self.ax_raw) # if self.xdata != None and self.ydata != None: self.ax_raw, = self.axes.plot(self.plotxdata, self.ydata, 'ro', linewidth=1, markersize=1) self.axes.relim() self.axes.autoscale() self.axes.set_xlabel(self.xPlot.currentText()) self.axes.set_ylabel(self.yFit.currentText()) # self.updatePlot() # self.plotWidget.cursor1.set_xdata(self.axes.get_xbound()[0]) #CHECK # self.plotWidget.cursor2.set_xdata(self.axes.get_xbound()[1]) #CHECK def updatePlot(self): self.axes.relim() self.axes.autoscale() self.fig.tight_layout() self.fig.canvas.draw() def plotSequence(self): self.plotRawData() self.update_cursor() self.fitData(False) # self.updatePlot() def update_cursor(self): if self.fit_range == [None,None]: self.fit_range[:] = [0,len(self.plotxdata)-1] if self.plotWidget.cursor1 != None: # x = self.plotxdata.min() x = self.plotxdata[self.fit_range[0]] y = [self.ydata.min(), self.ydata.max()] self.plotWidget.cursor1.set_xdata([x,x]) self.plotWidget.cursor1.set_ydata(y) #CHECK if self.plotWidget.cursor2 != None: # x = self.plotxdata.max() x = self.plotxdata[self.fit_range[1]-1] y = [self.ydata.min(), self.ydata.max()] self.plotWidget.cursor2.set_xdata([x,x]) self.plotWidget.cursor2.set_ydata(y) #CHECK # self.axes.relim() # self.axes.autoscale() self.updatePlot() self.plotWidget.draw_idle() # data fitting def fitData(self, update_slice = True): logging.debug("fit data") if self.enableFitting.isChecked() == True: self.generateFunction() #draw equation # if self.mathText != None: # self.fittingFunctionTEX.drawFigure(self.mathText) # self.updateTEX() if update_slice == True: xlim1 = min([self.plotWidget.cursor1.get_xdata()[0], self.plotWidget.cursor2.get_xdata()[0]]) xlim2 = max([self.plotWidget.cursor1.get_xdata()[0], self.plotWidget.cursor2.get_xdata()[0]]) self.fit_range[:] = [np.searchsorted(self.plotxdata, [xlim1])[0], np.searchsorted(self.plotxdata, [xlim2])[0]+1] logging.debug("inside") fit_slice = slice(*self.fit_range) logging.debug('%s', fit_slice) guess_vals = self.guessValues.text() l_bounds = self.lowBound.text() u_bounds = self.upBound.text() l_bounds_val = [float(_x) if _x != '' else -np.inf \ for _x in l_bounds.split(',')] \ if l_bounds != '' else -np.inf u_bounds_val = [float(_x) if _x != '' else np.inf \ for _x in u_bounds.split(',')] \ if u_bounds != '' else np.inf labeltext = self.fittingParams.text().replace(',', '=%5.3f, ') + \ '=%5.3f' # if self.ax_norm != None: # self.axes.lines.remove(self.ax_norm) if self.ax_constr != None: self.axes.lines.remove(self.ax_constr) try: logging.debug("test") #normal fit # popt, pcov = curve_fit(self.func, self.xdata[fit_slice], # self.ydata[fit_slice], # [float(x) for x in guess_vals.split(',')]) # print("normal", popt) # self.ax_norm, = self.axes.plot(self.xdata[fit_slice], # self.func(self.xdata[fit_slice], *popt), 'b-', # label= labeltext % tuple(popt)) #contrained fit popt, pcov = curve_fit(self.func, self.xdata[fit_slice], self.ydata[fit_slice], [float(_x) for _x in guess_vals.split(',')], bounds=(l_bounds_val,u_bounds_val)) logging.debug('%s, %s', "constrained", popt) self.fit_ydata = self.func(self.xdata[fit_slice], *popt) fit_label = labeltext % tuple(popt) self.ax_constr, = self.axes.plot(self.plotxdata[fit_slice], self.fit_ydata, 'g--', label= fit_label) error_label = 'Std. Dev. Error:\n' + labeltext % tuple(np.sqrt(np.diag(pcov))) self.fitResult.setText('Fit values:\n' + fit_label + '\n' + error_label) self.fitParams = dict(zip(self.fittingParams.text().split(','), popt)) logging.debug('%s', self.fitParams) except Exception as e: #on fitting failure logging.error(str(e)) self.fitResult.setText(str(e)) # self.ax_norm = None self.ax_constr = None # self.plotWidget.cursor1.set_ydata(self.axes.get_ybound()) #CHECK # self.plotWidget.cursor2.set_ydata(self.axes.get_ybound()) #CHECK self.axes.legend() # self.axes.text(1, 1, "Test", picker=5) ## self.fig.canvas.draw() else: if self.ax_constr != None: self.axes.get_legend().remove() self.axes.lines.remove(self.ax_constr) self.ax_constr = None self.fitParams = {} self.fitResult.setText('Fit values:\n') self.updatePlot()