class Login( QMainWindow ): # Окно логина. Если вход с этого ip уже осуществлён, то логин и пароль можно ввести любые def __init__(self): super().__init__() self.initUI() def initUI(self): uic.loadUi('ui/login.ui', self) self.stack = QStackedWidget() # Виджет для переключения между окнами self.stack.addWidget(self) self.stack.setWindowTitle("Barrelgram") self.stack.setCurrentWidget(self) self.requestThread = RequestThread() # Поток для запросов self.main = Main(self.requestThread) self.stack.addWidget(self.main) self.stack.setGeometry(self.geometry()) self.stack.show() self.requestThread.start() # Запуск потока self.button_login.clicked.connect(self.login) # self.login(raw=True) Планировалось сделать автоматический вход, но PyQt не хочет def login( self, raw=False ): # Параметр row нужен был для автоматического входа, если с этого ip уже вошли # Но PyQt не успевал так быстро переключать виджеты if (self.login_input.text() == "" or self.password_input.text() == "") and not raw: return data = { "login": self.login_input.text(), "password": self.password_input.text() } response = requests.post("http://" + SERVER_ADDRESS, data=data, params={ "app_client": True }).json() if response["verdict"] == "denied": if not raw: self.button_login.setText("Invalid data!") return else: if not raw: global SEND_GET_REQUESTS self.button_login.setText("Login") self.main.updateData(response, load_photo=True) self.stack.setCurrentWidget( self.main) # Переключение на основное окно SEND_GET_REQUESTS = True # Включение запросов
def keyPressEvent(self, e): if e.key() == Qt.Key_E: self.stw.setCurrentIndex(0) class ThreadClass(QThread): def __init__(self, callback, parent=None): super(ThreadClass, self).__init__(parent) self.point = 0 self.callback = callback def run(self): while 1: data = s.recv(1024) msg = data.decode('utf-8') if msg == 'goal': self.callback() #self.point += 10 #print(msg, self.point) if __name__ == "__main__": q = QApplication(sys.argv) st = QStackedWidget() st.addWidget(InitScene(st)) st.addWidget(PlayScene(st)) st.addWidget(RankingScene(st)) st.setGeometry(0, 0, 540, 960) st.show() sys.exit(q.exec_())
class AccountCreationPresenter(QWidget): """ Handles data-validation, saving, and transitions between frames """ should_load_main_app = pyqtSignal(Account) # Signal to reload LandingWindow def __init__(self, parent: QObject): super().__init__(parent) self.__layout_manager = QVBoxLayout(self) self.__slide_stack = QStackedWidget() # Stores the various frames for creating an account self.__proceed_btn = QPushButton("Get Started") # Changes to say next or finish, slide-depending # Connect events self.__proceed_btn.clicked.connect(self.__proceed_btn_was_clicked) self.__setup_ui() @QtCore.pyqtSlot() def __proceed_btn_was_clicked(self): curr_idx = self.__slide_stack.currentIndex() if curr_idx == 0 and self.__slide_stack.isHidden(): # Get Started, must show self.__slide_stack.show() self.__proceed_btn.setText("Next") elif self.__slide_stack.currentWidget().is_valid(): if curr_idx + 1 == self.__slide_stack.count() - 1: # Second to last slide\ self.__proceed_btn.setText("Finish") self.__slide_stack.setCurrentIndex(curr_idx + 1) elif curr_idx == self.__slide_stack.count() - 1: # Last slide # Save account data to global account = Account() for i in range(self.__slide_stack.count()): self.__slide_stack.widget(i).fill_account_details(account) write_to_data_file(DataType.USER, FileName.GLOBAL, account, False) # Port-forward, if approved self.hide() self.should_load_main_app.emit(account) else: self.__slide_stack.setCurrentIndex(curr_idx + 1) def __setup_ui(self): # Create frames and populate stack self.__slide_stack.addWidget(AccountDetailsFrame(self.__slide_stack)) self.__slide_stack.addWidget(NetworkDetailsFrame(self.__slide_stack)) self.__slide_stack.hide() # Set up welcome top-half content_frame = QFrame() content_frame.setFrameShape(QFrame.StyledPanel) content_frame.setFrameShadow(QFrame.Raised) content_frame.setObjectName("create-account-top") content_layout = QVBoxLayout(content_frame) # # Create labels welcome_lbl = QLabel('Welcome to UChat.') welcome_lbl.setObjectName('create-account-welcome') sub_lbl = QLabel('A secure and stateless, peer-to-peer messaging client') sub_lbl.setObjectName('sub-lbl') # # # Create separation line line = QFrame() line.setFixedHeight(1) line.setObjectName("line") line.setFrameShape(QFrame.HLine) line.setFrameShadow(QFrame.Sunken) # Configure proceed button self.__proceed_btn.setFixedSize(QSize(85, 35)) # # Layout content_layout.addWidget(welcome_lbl) content_layout.addWidget(sub_lbl) content_layout.addSpacing(10) content_layout.addWidget(line) content_layout.addWidget(self.__slide_stack) self.__layout_manager.addWidget(content_frame) self.__layout_manager.addSpacing(35) self.__layout_manager.addWidget(self.__proceed_btn) self.__layout_manager.addStretch()
import sys from PyQt5.QtWidgets import QApplication, QStackedWidget from PyQt5 import QtGui from Login.View.ViewLogin import ViewLogin if __name__ == '__main__': app = QApplication(sys.argv) widget = QStackedWidget() Vista_login = ViewLogin(widget) widget.addWidget(Vista_login) widget.setStyleSheet("background-color: rgb(254, 235, 156)") widget.setWindowTitle("Cinema") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap("images/biglietto.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) widget.setWindowIcon(icon) # Spostamento widget al centro (fissare dimensione login al max per mantenere tutto al centro) widget.show() centerPoint = QtGui.QScreen.availableGeometry(app.primaryScreen()).center() fg = widget.frameGeometry() fg.moveCenter(centerPoint) widget.move(fg.topLeft()) sys.exit(app.exec())
class MainView(base, form): def __init__(self, pipeline, parent=None): super(base, self).__init__(parent) self.setupUi(self) self.pipeline = pipeline self.pip_widgets = [] self.default_pips = [] self.draw_ui() self.connect_ui() def register_observers(self): pass def connect_ui(self): """ This function connects the ui using signals from the ui elements and its method counterparts. """ self.input_btn.clicked.connect(self.set_input_url) self.output_btn.clicked.connect(self.set_output_url) self.save_btn.clicked.connect(self.save_pipeline) self.load_favorite_pipelines() self.fav_pips_combo_box.activated.connect(self.select_default_pip) self.run_btn.clicked.connect(self.run) self.delete_btn.clicked.connect(self.trash_pipeline) self.add_btn.clicked.connect(lambda: self.add_pipe_entry_new()) def draw_ui(self): """ This function draws all additional UI elements. If you want the application to display any additional things like a button you can either add it in the QtDesigner or declare it here. """ # *TODO* Create these ones with Qt Designer and put them into select_cat_alg_vbox_layout. I failed self.ComboxCategories = QComboBox() self.stackedWidgetComboxesAlgorithms = QStackedWidget() self.select_cat_alg_vbox_layout.addWidget(self.ComboxCategories) self.select_cat_alg_vbox_layout.addWidget(self.stackedWidgetComboxesAlgorithms) self.ComboxCategories.hide() """ This function is concerned with drawing all non static elements into the GUI. """ """self.set_pip_title("A. Junius2") self.set_preset(["A.Junius", "test", "test", "test"]) self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_pip_entry("../assets/images/P.png", "Preprocessing - adaptive trehsold watershed") self.add_cat_image("../assets/images/seg_fav.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.add_cat_image("../assets/images/wing.jpeg", "Preprocessing") self.main_image_label.setPixmap(QtGui.QPixmap("wing.jpeg")) category_combo_box = ComboBoxWidget("type") category_combo_box.add_item("Preprocessing", "../assets/images/P.png") category_combo_box.add_item("Segmentation", "../assets/images/S.png") category_combo_box.add_item("Graph Detection", "../assets/images/D.png") category_combo_box.add_item("Graph Filtering", "../assets/images/F.png") alg_combo_box = ComboBoxWidget("algorithm") alg_combo_box.add_item("Otsus") alg_combo_box.add_item("Guo Hall") alg_combo_box.add_item("Adaptive Treshold") slider_1 = SliderWidget("slider1das", 0, 10, 1, 4, True) slider_2 = SliderWidget("slider1", 0, 10, 2, 4, False) slider_3 = SliderWidget("sliderböadsad", 0, 10, 1, 4, True) slider_4 = SliderWidget("sliderböadsad", 0, 10, 1, 4, True) slider_5 = SliderWidget("sliderböadsad", 0, 10, 1, 4, True) checkbox_1 = CheckBoxWidget("checkbox1", True) self.setting_widget_vbox_layout.addWidget(category_combo_box) self.setting_widget_vbox_layout.addWidget(alg_combo_box) self.setting_widget_vbox_layout.addWidget(slider_1) self.setting_widget_vbox_layout.addWidget(slider_2) self.setting_widget_vbox_layout.addWidget(slider_3) self.setting_widget_vbox_layout.addWidget(slider_4) self.setting_widget_vbox_layout.addWidget(slider_5) self.setting_widget_vbox_layout.addWidget(checkbox_1) self.setting_widget_vbox_layout.setAlignment(Qt.AlignTop)""" def set_pip_title(self, title): """ Sets the title of the current selected pipeline in the ui. Args: | *title*: the title of the pipeline | *label_ref*: the reference to the label. """ self.current_pip_label.setText(title) def load_dark_theme(self, application): """ This function is called to load the white theme with all its icons for the buttons and the css file. Args: application: the cureent app instance """ # load buttons pixmap_icon = QtGui.QPixmap("./assets/images/add_white.png") q_icon = QtGui.QIcon(pixmap_icon) self.add_btn.setIcon(q_icon) pixmap_icon = QtGui.QPixmap("./assets/images/trash_white.png") q_icon = QtGui.QIcon(pixmap_icon) self.delete_btn.setIcon(q_icon) pixmap_icon = QtGui.QPixmap("./assets/images/diskette_white.png") q_icon = QtGui.QIcon(pixmap_icon) self.save_btn.setIcon(q_icon) pixmap_icon = QtGui.QPixmap("./assets/images/up-arrow_white.png") q_icon = QtGui.QIcon(pixmap_icon) self.input_btn.setIcon(q_icon) pixmap_icon = QtGui.QPixmap("./assets/images/folder_white.png") q_icon = QtGui.QIcon(pixmap_icon) self.output_btn.setIcon(q_icon) @pyqtSlot(int) def select_default_pip(self, index): """ This is the slot for the Pipeline combobox in the ui Args: index: index of the option currently selected """ # delete current pipeline self.trash_pipeline() # get url and name name, url = self.default_pips[index - 1] # parse the json in the model self.pipeline.load_pipeline_json(url) print("PARSER" + str(self.pipeline.executed_cats[0].active_algorithm)) print("PARSER" + str(self.pipeline.executed_cats[1].active_algorithm)) # set the title self.set_pip_title(name) # Create an entry in the pipeline widget for every step in the pipeline for i in range(0, len(self.pipeline.executed_cats)): self.add_pipe_entry_new(i) self.scroll_down_pip() """for widget in alg_widgets: self.setting_widget_vbox_layout.addWidget(widget)""" def trash_pipeline(self): """ This method clears the complete pipeline while users clicked the trash button. """ # remove all entries in the pipeline list while self.pip_widget_vbox_layout.count(): child = self.pip_widget_vbox_layout.takeAt(0) child.widget().deleteLater() while self.stackedWidget_Settings.currentWidget() is not None: self.stackedWidget_Settings.removeWidget(self.stackedWidget_Settings.currentWidget()) self.settings_collapsable.setTitle("") # remove the pipeline name self.set_pip_title("") # remove all entries int the executed_cats of the model pipeline del self.pipeline.executed_cats[:] # remove all widgets del self.pip_widgets[:] # remove category algorith dropdown self.remove_cat_alg_dropdown() # remove all entries from the pipeline model del self.pipeline.executed_cats[:] @pyqtSlot() def run(self): """ This method runs the the pipeline by calling the process methode in pipeline """ self.pipeline.process() @pyqtSlot() def set_input_url(self): """ This method sets the url for the input image in the pipeline. """ url = QtWidgets.QFileDialog.getOpenFileNames() if url[0]: print(url[0]) print(url[0][0]) self.lineEdit.setText(url[0][0]) self.pipeline.set_input(url[0][0]) @pyqtSlot() def set_output_url(self): """ This method sets the url for the output folder in the pipeline. Args: url: the url to the output folder a user selected in the ui """ url = QtWidgets.QFileDialog.getExistingDirectory() if url: print(url) print(url) self.custom_line_edit.setText(url) self.pipeline.set_output_dir(url) def load_favorite_pipelines(self): """ Scans the directory for default pipelines to display all available items """ self.fav_pips_combo_box.addItem("Please Select") # scan the directory for default pipelines for file in os.listdir("./_default_pipelines"): if file.endswith(".json"): name = file.split(".")[0] url = os.path.abspath("./_default_pipelines" + "/" + file) self.default_pips.append([name, url]) self.fav_pips_combo_box.addItem(name) @pyqtSlot() def save_pipeline(self): """ Saves the pipeline as a json at the users file system. """ url = str(QtWidgets.QFileDialog.getSaveFileName()[0]) split_list = url.split("/") name = split_list[len(split_list) - 1].split(".")[0] del split_list[len(split_list) - 1] url = url.replace(name, "") self.pipeline.save_pipeline_json(name, url) @pyqtSlot(int) def remove_pip_entry(self, pipe_entry_widget, settings_widget, cat=None): """ Removes the pip entry at the given position in the ui Args: pipeline_index (object): settings_widget: position: position at which the pip entry gets removed """ # remove pipeline entry widget from ui self.pip_widget_vbox_layout.removeWidget(pipe_entry_widget) pipe_entry_widget.deleteLater() # remove it settings widgets from ui if settings_widget is not None: if self.stackedWidget_Settings.currentWidget() == settings_widget: self.stackedWidget_Settings.hide() self.remove_cat_alg_dropdown() self.settings_collapsable.setTitle("Settings") self.stackedWidget_Settings.removeWidget(settings_widget) # remove in model if cat is not None: print("Remove entry at pos " + str(self.pipeline.get_index(cat)) + " " + str(cat)) self.pipeline.delete_category(self.pipeline.get_index(cat)) def change_pip_entry_alg(self, position, new_category, new_algorithm, pipe_entry_widget, settings_widget): """ Changes the selected algorithm of the pipeline entry at the position. Afterwards create all widgets for this algorithm instance Args: position: the position of the pipeline entry algorithm: the selected algorithm for this category """ print("Position to be changed:" + str(position)) print("Pipeline length: " + str(len(self.pipeline.executed_cats))) old_cat = self.pipeline.executed_cats[position] old_alg = old_cat.active_algorithm print("Old Cat found in pipeline: " + str(old_cat)) print("Old Alg: found in pipeline:" + str(old_alg)) print("New Category given:" + str(new_category)) print("New Algorithm given:" + str(new_algorithm)) # set in model self.pipeline.change_category(new_category, position) self.pipeline.change_algorithm(new_algorithm, position) new_cat = self.pipeline.executed_cats[position] new_alg = new_cat.active_algorithm # change settings widgets self.remove_pip_entry(pipe_entry_widget, settings_widget) (new_pipe_entry_widget, new_settings_widget) = self.add_pipe_entry_new(position) self.stackedWidget_Settings.show() self.stackedWidget_Settings.setCurrentIndex(position) self.settings_collapsable.setTitle(new_alg.get_name() + " Settings") self.remove_cat_alg_dropdown() self.create_cat_alg_dropdown(position, new_pipe_entry_widget, new_settings_widget) self.set_cat_alg_dropdown(new_cat, new_alg) print("New Cat found in pipeline: " + str(new_cat)) print("New Alg found in pipeline: " + str(new_alg)) def load_settings_widgets_from_pipeline_groupbox(self, position): """ Extracts all widgets from a single algorithm and returns a QBoxLayout Args: alg: the alg instance we extract from Returns: a QBoxLayout containing all widgets for this particular alg. """ alg = self.pipeline.executed_cats[position].active_algorithm print("alg " + str(alg)) print("cat " + str(self.pipeline.executed_cats[position])) empty_flag = True groupOfSliders = QGroupBox() sp = QSizePolicy() sp.setVerticalPolicy(QSizePolicy.Preferred) # groupOfSliders.setSizePolicy(sp) groupOfSliderssLayout = QBoxLayout(QBoxLayout.TopToBottom) groupOfSliderssLayout.setContentsMargins(0, -0, -0, 0) groupOfSliderssLayout.setAlignment(Qt.AlignTop) groupOfSliderssLayout.setSpacing(0) print("Build Slider @ "+ str(position)) # create integer sliders for slider in alg.integer_sliders: empty_flag = False print("slider.value " + str(slider.value)) print("slider " + str(slider)) #print(alg.get_name() + ": add slider (int).") groupOfSliderssLayout.addWidget( SliderWidget(slider.name, slider.lower, slider.upper, slider.step_size, slider.value, slider.set_value, False)) # create float sliders for slider in alg.float_sliders: empty_flag = False #print(alg.get_name() + ": add slider (float).") groupOfSliderssLayout.addWidget( SliderWidget(slider.name, slider.lower, slider.upper, slider.step_size, slider.value, slider.set_value, True), 0, Qt.AlignTop) # create checkboxes for checkbox in alg.checkboxes: empty_flag = False #print(alg.get_name() + ": add checkbox.") groupOfSliderssLayout.addWidget(CheckBoxWidget(checkbox.name, checkbox.value, checkbox.set_value), 0, Qt.AlignTop) # create dropdowns for combobox in alg.drop_downs: empty_flag = False #print(alg.get_name() + ": add combobox.") groupOfSliderssLayout.addWidget( ComboBoxWidget(combobox.name, combobox.options, combobox.set_value, combobox.value), 0, Qt.AlignTop) if empty_flag: label = QLabel() label.setText("This algorithm has no Settings.") groupOfSliderssLayout.addWidget(label, 0, Qt.AlignHCenter) groupOfSliders.setLayout(groupOfSliderssLayout) return groupOfSliders def create_cat_alg_dropdown(self, cat_position, pipe_entry_widget, settings_widget): """ Args: last_cat (object): """ layout = self.select_cat_alg_vbox_layout cat = self.pipeline.executed_cats[cat_position] last_cat = None # Show only allowed categories in dropdown if len(self.pipeline.executed_cats) > 1: last_cat = self.pipeline.executed_cats[cat_position - 1] # Combobox for selecting Category self.ComboxCategories.show() self.ComboxCategories.setFixedHeight(30) self.ComboxCategories.addItem("<Please Select Category>") self.stackedWidgetComboxesAlgorithms = QStackedWidget() self.stackedWidgetComboxesAlgorithms.setFixedHeight(30) self.stackedWidgetComboxesAlgorithms.hide() def setCurrentIndexCat(index): #print("Set Cat") if self.ComboxCategories.currentIndex() == 0: self.stackedWidgetComboxesAlgorithms.hide() else: self.stackedWidgetComboxesAlgorithms.show() self.stackedWidgetComboxesAlgorithms.setCurrentIndex(index - 1) for category_name in self.pipeline.report_available_cats(last_cat): # Add Category to combobox self.ComboxCategories.addItem(category_name) tmp1 = QComboBox() tmp1.addItem("<Please Select Algorithm>") tmp1.setFixedHeight(30) category = self.pipeline.get_category(category_name) #self.current_index = -1 def setCurrentIndexAlg(index): if self.ComboxCategories.currentIndex() == 0 or self.stackedWidgetComboxesAlgorithms.currentWidget().currentIndex() == 0: pass else: #self.current_index != index: self.change_pip_entry_alg(self.pipeline.get_index(cat), self.ComboxCategories.currentText(), self.stackedWidgetComboxesAlgorithms.currentWidget().currentText(), pipe_entry_widget, settings_widget) #self.current_index = index tmp1.activated.connect(setCurrentIndexAlg) for algorithm_name in self.pipeline.get_all_algorithm_list(category): tmp1.addItem(algorithm_name) self.stackedWidgetComboxesAlgorithms.addWidget(tmp1) layout.addWidget(self.ComboxCategories) layout.addWidget(self.stackedWidgetComboxesAlgorithms) self.ComboxCategories.activated.connect(setCurrentIndexCat) def set_cat_alg_dropdown(self, category, algorithm): indexC = self.ComboxCategories.findText(category.get_name()) #print("IndexC " + str(indexC)) self.ComboxCategories.setCurrentIndex(indexC) self.stackedWidgetComboxesAlgorithms.show() self.stackedWidgetComboxesAlgorithms.setCurrentIndex(indexC - 1) indexA = self.stackedWidgetComboxesAlgorithms.currentWidget().findText(algorithm.get_name()) #print("IndexA " + str(indexA)) self.stackedWidgetComboxesAlgorithms.currentWidget().setCurrentIndex(indexA) def remove_cat_alg_dropdown(self): """ Returns: object: """ self.ComboxCategories.clear() while self.stackedWidgetComboxesAlgorithms.currentWidget() is not None: self.stackedWidgetComboxesAlgorithms.removeWidget(self.stackedWidgetComboxesAlgorithms.currentWidget()) while self.select_cat_alg_vbox_layout.count(): child = self.select_cat_alg_vbox_layout.takeAt(0) child.widget().hide() def scroll_down_pip(self): self.pip_scroll.verticalScrollBar().setSliderPosition(self.pip_scroll.verticalScrollBar().maximum()) def add_pipe_entry_new(self, position=None): """ Creates a entry in the ui pipeline with a given position in pipeline. It also creates the corresponding settings widget. """ # create an widget that displays the pip entry in the ui and connect the remove button pip_main_widget = QWidget() pip_main_widget.setFixedHeight(70) pip_main_widget.setFixedWidth(300) pip_main_layout = QHBoxLayout() pip_main_widget.setLayout(pip_main_layout) new_marker = False if position is None: position = len(self.pipeline.executed_cats) cat = self.pipeline.new_category(position) label = "<Click to specify new step>" icon = None new_marker = True else: cat = self.pipeline.executed_cats[position] alg = cat.active_algorithm label = alg.get_name() icon = cat.get_icon() new_marker = False pixmap = QPixmap(icon) pixmap_scaled_keeping_aspec = pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio) pixmap_label = QtWidgets.QLabel() pixmap_label.setPixmap(pixmap_scaled_keeping_aspec) pip_up_down = QWidget() pip_up_down.setFixedHeight(70) pip_up_down_layout = QVBoxLayout() pip_up_down.setLayout(pip_up_down_layout) up_btn = QToolButton() dw_btn = QToolButton() up_btn.setArrowType(Qt.UpArrow) up_btn.setFixedHeight(25) dw_btn.setArrowType(Qt.DownArrow) dw_btn.setFixedHeight(25) pip_up_down_layout.addWidget(up_btn) pip_up_down_layout.addWidget(dw_btn) string_label = QLabel() string_label.setText(label) string_label.setFixedWidth(210) btn = QtWidgets.QPushButton() btn.setFixedSize(20, 20) pixmap_icon = QtGui.QPixmap("./assets/images/delete_x_white.png") q_icon = QtGui.QIcon(pixmap_icon) btn.setIcon(q_icon) pip_main_layout.addWidget(pip_up_down, Qt.AlignVCenter) pip_main_layout.addWidget(pixmap_label, Qt.AlignVCenter) pip_main_layout.addWidget(string_label, Qt.AlignLeft) pip_main_layout.addWidget(btn, Qt.AlignVCenter) self.pip_widget_vbox_layout.insertWidget(position, pip_main_widget) # Create the corresponding settings widget and connect it self.settings_collapsable.setTitle("Settings") self.stackedWidget_Settings.hide() settings_main_widget = None if not new_marker: settings_main_widget = self.load_settings_widgets_from_pipeline_groupbox(position) self.stackedWidget_Settings.insertWidget(position, settings_main_widget) def show_settings(): # Set background color while widget is selected. Doesn't work because of theme? *TODO* p = pip_main_widget.palette() p.setColor(pip_main_widget.backgroundRole(), Qt.red) pip_main_widget.setPalette(p) if not new_marker: self.stackedWidget_Settings.show() self.stackedWidget_Settings.setCurrentIndex(self.pipeline.get_index(cat)) self.settings_collapsable.setTitle(alg.get_name() + " Settings") else: self.stackedWidget_Settings.hide() # Create drop down for cats and algs self.remove_cat_alg_dropdown() self.create_cat_alg_dropdown(self.pipeline.get_index(cat), pip_main_widget, settings_main_widget) if not new_marker: self.set_cat_alg_dropdown(cat, alg) # Connect Button to remove step from pipeline def delete_button_clicked(): self.remove_cat_alg_dropdown() self.remove_pip_entry(pip_main_widget, settings_main_widget, cat) self.clickable(pixmap_label).connect(show_settings) self.clickable(string_label).connect(show_settings) btn.clicked.connect(delete_button_clicked) return (pip_main_widget, settings_main_widget) def add_pip_entry_empty(self): """ Creates an blank entry in the ui pipeline since the user still needs to specify a type and an algorithm of the category. It also creates the corresponding settings widget. """ # create an widget that displays the pip entry in the ui and connect the remove button pip_main_widget = QWidget() pip_main_widget.setFixedHeight(70) pip_main_widget.setFixedWidth(300) pip_main_layout = QHBoxLayout() pip_main_widget.setLayout(pip_main_layout) label = "<Click to specify new step>" icon = None pixmap = QPixmap(icon) pixmap_scaled_keeping_aspec = pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio) pixmap_label = QtWidgets.QLabel() pixmap_label.setPixmap(pixmap_scaled_keeping_aspec) pip_up_down = QWidget() pip_up_down.setFixedHeight(70) pip_up_down_layout = QVBoxLayout() pip_up_down.setLayout(pip_up_down_layout) up_btn = QToolButton() dw_btn = QToolButton() up_btn.setArrowType(Qt.UpArrow) up_btn.setFixedHeight(25) dw_btn.setArrowType(Qt.DownArrow) dw_btn.setFixedHeight(25) pip_up_down_layout.addWidget(up_btn) pip_up_down_layout.addWidget(dw_btn) string_label = QLabel() string_label.setText(label) string_label.setFixedWidth(210) btn = QtWidgets.QPushButton() btn.setFixedSize(20, 20) pixmap_icon = QtGui.QPixmap("./assets/images/delete_x_white.png") q_icon = QtGui.QIcon(pixmap_icon) btn.setIcon(q_icon) pip_main_layout.addWidget(pip_up_down, Qt.AlignVCenter) pip_main_layout.addWidget(pixmap_label, Qt.AlignVCenter) pip_main_layout.addWidget(string_label, Qt.AlignLeft) pip_main_layout.addWidget(btn, Qt.AlignVCenter) cat_position = len(self.pipeline.executed_cats) self.pip_widget_vbox_layout.insertWidget(cat_position, pip_main_widget) index = self.pip_widget_vbox_layout.indexOf(pip_main_widget) #print(index) # Create the corresponding empty settings widget and connect it # settings = self.load_widgets_from_cat_groupbox(cat_position) *TODO* EMPTY self.settings_collapsable.setTitle("Settings") self.stackedWidget_Settings.hide() # Add new step to pipeline new_category = self.pipeline.new_category(cat_position) print("Create new entry " + str(new_category)) print("Pipeline length: " + str(len(self.pipeline.executed_cats)) + ".") settings_main_widget = None # Connect pipeline entry with corresponding settings widget def show_settings(): #print("click") self.stackedWidget_Settings.show() self.remove_cat_alg_dropdown() # Create drop down for cats and algs self.create_cat_alg_dropdown(self.pipeline.get_index(new_category), pip_main_widget, settings_main_widget) self.stackedWidget_Settings.hide() # Connect Button to remove step from pipeline def delete_button_clicked(): self.remove_cat_alg_dropdown() self.remove_pip_entry(pip_main_widget, settings_main_widget, new_category) self.clickable(pixmap_label).connect(show_settings) self.clickable(string_label).connect(show_settings) btn.clicked.connect(delete_button_clicked) self.scroll_down_pip() def add_pip_entry(self, cat_position): """ Creates a entry in the ui pipeline with a given position in pipeline. It also creates the corresponding settings widget. """ # create an widget that displays the pip entry in the ui and connect the remove button pip_main_widget = QWidget() pip_main_widget.setFixedHeight(70) pip_main_widget.setFixedWidth(300) pip_main_layout = QHBoxLayout() pip_main_widget.setLayout(pip_main_layout) cat = self.pipeline.executed_cats[cat_position] alg = cat.active_algorithm label = alg.get_name() icon = cat.get_icon() pixmap = QPixmap(icon) pixmap_scaled_keeping_aspec = pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio) pixmap_label = QtWidgets.QLabel() pixmap_label.setPixmap(pixmap_scaled_keeping_aspec) pip_up_down = QWidget() pip_up_down.setFixedHeight(70) pip_up_down_layout = QVBoxLayout() pip_up_down.setLayout(pip_up_down_layout) up_btn = QToolButton() dw_btn = QToolButton() up_btn.setArrowType(Qt.UpArrow) up_btn.setFixedHeight(25) dw_btn.setArrowType(Qt.DownArrow) dw_btn.setFixedHeight(25) pip_up_down_layout.addWidget(up_btn) pip_up_down_layout.addWidget(dw_btn) string_label = QLabel() string_label.setText(label) string_label.setFixedWidth(210) btn = QtWidgets.QPushButton() btn.setFixedSize(20, 20) pixmap_icon = QtGui.QPixmap("./assets/images/delete_x_white.png") q_icon = QtGui.QIcon(pixmap_icon) btn.setIcon(q_icon) pip_main_layout.addWidget(pip_up_down, Qt.AlignVCenter) pip_main_layout.addWidget(pixmap_label, Qt.AlignVCenter) pip_main_layout.addWidget(string_label, Qt.AlignLeft) pip_main_layout.addWidget(btn, Qt.AlignVCenter) self.pip_widget_vbox_layout.insertWidget(cat_position, pip_main_widget) index = self.pip_widget_vbox_layout.indexOf(pip_main_widget) #print(index) # Create the corresponding settings widget and connect it settings_main_widget = self.load_settings_widgets_from_pipeline_groupbox(cat_position) self.settings_collapsable.setTitle("Settings") self.stackedWidget_Settings.hide() self.stackedWidget_Settings.insertWidget(cat_position, settings_main_widget) #print("Read from pipeline entry " + str(cat)) #print("Pipeline length: " + str(len(self.pipeline.executed_cats)) + ".") def show_settings(): # Set background color while widget is selected. Doesn't work because of theme? *TODO* p = pip_main_widget.palette() p.setColor(pip_main_widget.backgroundRole(), Qt.red) pip_main_widget.setPalette(p) self.stackedWidget_Settings.show() self.stackedWidget_Settings.setCurrentIndex(self.pipeline.get_index(cat)) self.settings_collapsable.setTitle(alg.get_name() + " Settings") self.remove_cat_alg_dropdown() # Create drop down for cats and algs self.create_cat_alg_dropdown(self.pipeline.get_index(cat), pip_main_widget, settings_main_widget) #print(cat) #print(alg) self.set_cat_alg_dropdown(cat, alg) # Connect Button to remove step from pipeline def delete_button_clicked(): self.remove_pip_entry(pip_main_widget, settings_main_widget, cat) self.clickable(pixmap_label).connect(show_settings) self.clickable(string_label).connect(show_settings) btn.clicked.connect(delete_button_clicked) return (pip_main_widget, settings_main_widget) # https://wiki.python.org/moin/PyQt/Making%20non-clickable%20widgets%20clickable def clickable(self, widget): """ Convert any widget to a clickable widget. """ class Filter(QObject): clicked = pyqtSignal() def eventFilter(self, obj, event): if obj == widget: if event.type() == QEvent.MouseButtonPress: if obj.rect().contains(event.pos()): self.clicked.emit() # The developer can opt for .emit(obj) to get the object within the slot. return True return False filter = Filter(widget) widget.installEventFilter(filter) return filter.clicked
class CollapsibleTabWidget(QWidget): Horizontal = 0 Vertical = 1 doCollapse = pyqtSignal() def __init__(self, orientation=0, parent=None): super(CollapsibleTabWidget, self).__init__(parent=parent) self.frameLayout = None self.verticalLayout = None self.tabBar = None self.tabBarWidget = QWidget(self) self.orientation = orientation # self.orientation = self.Vertical self.splitter = None self.splitterPos = None self.splitterLower = None self.stackTitle = None self.stackWidget = None self.tabBarList = [] # local data if self.orientation == self.Horizontal: self.initHorizontalUI() self.titleBarIcon = TitleBar.down elif self.orientation == self.Vertical: self.initVerticalUI() self.titleBarIcon = TitleBar.left self.tabBarWidget.setStyleSheet('background-color: #B2B2B2;') self.stackTitle.setStyleSheet('background-color: #B2B2B2;') def initHorizontalUI(self): self.frameLayout = QVBoxLayout(self) self.tabBar = QHBoxLayout(self) self.tabBarWidget.setLayout(self.tabBar) self.tabBar.setAlignment(Qt.AlignLeft) self.verticalLayout = QVBoxLayout(self) # fill stack self.stackTitle = QStackedWidget(self) self.stackWidget = QStackedWidget(self) self.verticalLayout.addWidget(self.stackTitle) self.verticalLayout.addWidget(self.stackWidget) # finish self.frameLayout.addLayout(self.verticalLayout) self.frameLayout.addWidget(self.tabBarWidget) self.setLayout(self.frameLayout) self.tabBarWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) def initVerticalUI(self): self.frameLayout = QHBoxLayout(self) self.verticalLayout = QVBoxLayout(self) # tab bar self.tabBar = QVBoxLayout(self) self.tabBarWidget.setLayout(self.tabBar) self.tabBar.setAlignment(Qt.AlignTop) # fill stack self.stackTitle = QStackedWidget(self) self.stackWidget = QStackedWidget(self) self.verticalLayout.addWidget(self.stackTitle) self.verticalLayout.addWidget(self.stackWidget) self.stackWidget.addWidget(QLabel('asdf', self)) # finish self.frameLayout.addWidget(self.tabBarWidget) self.frameLayout.addLayout(self.verticalLayout) self.setLayout(self.frameLayout) self.tabBarWidget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding) def setOrientation(self, orient): self.orientation = orient def onTabClicked(self, index): pass def addTab(self, widget: QWidget, title: str): titleBar = TitleBar(title, self) titleBar.setButtonOrient(self.titleBarIcon) titleBar.CollapseButton.clicked.connect(self.collapseStacks) self.stackTitle.addWidget(titleBar) self.stackWidget.addWidget(widget) tabButton = customPushButton(title, len(self.tabBarList), self.orientation, self) self.tabBarList.append(tabButton) tabButton.clicked.connect(self.collapseStacks) tabButton.clicked_index.connect(self.setCurStack) self.tabBar.addWidget(tabButton, 0, Qt.AlignLeft) self.stackTitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.stackTitle.setFixedHeight(titleBar.Height) def collapseStacks(self): if self.stackWidget.isVisible(): self.splitterPos = self.splitter.sizes() self.stackTitle.hide() self.stackWidget.hide() if self.orientation == self.Horizontal: self.splitter.setSizes([10000, 0]) if self.orientation == self.Vertical: self.splitter.setSizes([0, 10000]) self.splitter.handle(1).setDisabled(True) else: self.splitter.setSizes(self.splitterPos) self.stackTitle.show() self.stackWidget.show() self.splitter.handle(1).setDisabled(False) self.doCollapse.emit() def setCurStack(self, index): self.stackTitle.setCurrentIndex(index) self.stackWidget.setCurrentIndex(index) def setSplitter(self, splitter: QSplitter): self.splitter = splitter self.splitter.splitterMoved.connect(self.setSplitterRate) def setSplitterRate(self, pos, index): self.splitterLower = self.splitter.sizes()[1]
class E5SideBar(QWidget): """ Class implementing a sidebar with a widget area, that is hidden or shown, if the current tab is clicked again. """ Version = 1 North = 0 East = 1 South = 2 West = 3 def __init__(self, orientation=None, delay=200, parent=None): """ Constructor @param orientation orientation of the sidebar widget (North, East, South, West) @param delay value for the expand/shrink delay in milliseconds (integer) @param parent parent widget (QWidget) """ super(E5SideBar, self).__init__(parent) self.__tabBar = QTabBar() self.__tabBar.setDrawBase(True) self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setUsesScrollButtons(True) self.__tabBar.setDrawBase(False) self.__stackedWidget = QStackedWidget(self) self.__stackedWidget.setContentsMargins(0, 0, 0, 0) self.__autoHideButton = QToolButton() self.__autoHideButton.setCheckable(True) self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) self.__autoHideButton.setChecked(True) self.__autoHideButton.setToolTip( self.tr("Deselect to activate automatic collapsing")) self.barLayout = QBoxLayout(QBoxLayout.LeftToRight) self.barLayout.setContentsMargins(0, 0, 0, 0) self.layout = QBoxLayout(QBoxLayout.TopToBottom) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.barLayout.addWidget(self.__autoHideButton) self.barLayout.addWidget(self.__tabBar) self.layout.addLayout(self.barLayout) self.layout.addWidget(self.__stackedWidget) self.setLayout(self.layout) # initialize the delay timer self.__actionMethod = None self.__delayTimer = QTimer(self) self.__delayTimer.setSingleShot(True) self.__delayTimer.setInterval(delay) self.__delayTimer.timeout.connect(self.__delayedAction) self.__minimized = False self.__minSize = 0 self.__maxSize = 0 self.__bigSize = QSize() self.splitter = None self.splitterSizes = [] self.__hasFocus = False # flag storing if this widget or any child has the focus self.__autoHide = False self.__tabBar.installEventFilter(self) self.__orientation = E5SideBar.North if orientation is None: orientation = E5SideBar.North self.setOrientation(orientation) self.__tabBar.currentChanged[int].connect( self.__stackedWidget.setCurrentIndex) e5App().focusChanged[QWidget, QWidget].connect(self.__appFocusChanged) self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled) def setSplitter(self, splitter): """ Public method to set the splitter managing the sidebar. @param splitter reference to the splitter (QSplitter) """ self.splitter = splitter self.splitter.splitterMoved.connect(self.__splitterMoved) self.splitter.setChildrenCollapsible(False) index = self.splitter.indexOf(self) self.splitter.setCollapsible(index, False) def __splitterMoved(self, pos, index): """ Private slot to react on splitter moves. @param pos new position of the splitter handle (integer) @param index index of the splitter handle (integer) """ if self.splitter: self.splitterSizes = self.splitter.sizes() def __delayedAction(self): """ Private slot to handle the firing of the delay timer. """ if self.__actionMethod is not None: self.__actionMethod() def setDelay(self, delay): """ Public method to set the delay value for the expand/shrink delay in milliseconds. @param delay value for the expand/shrink delay in milliseconds (integer) """ self.__delayTimer.setInterval(delay) def delay(self): """ Public method to get the delay value for the expand/shrink delay in milliseconds. @return value for the expand/shrink delay in milliseconds (integer) """ return self.__delayTimer.interval() def __cancelDelayTimer(self): """ Private method to cancel the current delay timer. """ self.__delayTimer.stop() self.__actionMethod = None def shrink(self): """ Public method to record a shrink request. """ self.__delayTimer.stop() self.__actionMethod = self.__shrinkIt self.__delayTimer.start() def __shrinkIt(self): """ Private method to shrink the sidebar. """ self.__minimized = True self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() if self.splitter: self.splitterSizes = self.splitter.sizes() self.__stackedWidget.hide() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.setFixedHeight(self.__tabBar.minimumSizeHint().height()) else: self.setFixedWidth(self.__tabBar.minimumSizeHint().width()) self.__actionMethod = None def expand(self): """ Public method to record a expand request. """ self.__delayTimer.stop() self.__actionMethod = self.__expandIt self.__delayTimer.start() def __expandIt(self): """ Private method to expand the sidebar. """ self.__minimized = False self.__stackedWidget.show() self.resize(self.__bigSize) if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = max(self.__minSize, self.minimumSizeHint().height()) self.setMinimumHeight(minSize) self.setMaximumHeight(self.__maxSize) else: minSize = max(self.__minSize, self.minimumSizeHint().width()) self.setMinimumWidth(minSize) self.setMaximumWidth(self.__maxSize) if self.splitter: self.splitter.setSizes(self.splitterSizes) self.__actionMethod = None def isMinimized(self): """ Public method to check the minimized state. @return flag indicating the minimized state (boolean) """ return self.__minimized def isAutoHiding(self): """ Public method to check, if the auto hide function is active. @return flag indicating the state of auto hiding (boolean) """ return self.__autoHide def eventFilter(self, obj, evt): """ Public method to handle some events for the tabbar. @param obj reference to the object (QObject) @param evt reference to the event object (QEvent) @return flag indicating, if the event was handled (boolean) """ if obj == self.__tabBar: if evt.type() == QEvent.MouseButtonPress: pos = evt.pos() for i in range(self.__tabBar.count()): if self.__tabBar.tabRect(i).contains(pos): break if i == self.__tabBar.currentIndex(): if self.isMinimized(): self.expand() else: self.shrink() return True elif self.isMinimized(): self.expand() elif evt.type() == QEvent.Wheel: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta > 0: self.prevTab() else: self.nextTab() return True return QWidget.eventFilter(self, obj, evt) def addTab(self, widget, iconOrLabel, label=None): """ Public method to add a tab to the sidebar. @param widget reference to the widget to add (QWidget) @param iconOrLabel reference to the icon or the label text of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.addTab(iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.addTab(iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.addWidget(widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def insertTab(self, index, widget, iconOrLabel, label=None): """ Public method to insert a tab into the sidebar. @param index the index to insert the tab at (integer) @param widget reference to the widget to insert (QWidget) @param iconOrLabel reference to the icon or the labeltext of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.insertTab(index, iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.insertTab(index, iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.insertWidget(index, widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def removeTab(self, index): """ Public method to remove a tab. @param index the index of the tab to remove (integer) """ self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) self.__tabBar.removeTab(index) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def clear(self): """ Public method to remove all tabs. """ while self.count() > 0: self.removeTab(0) def prevTab(self): """ Public slot used to show the previous tab. """ ind = self.currentIndex() - 1 if ind == -1: ind = self.count() - 1 self.setCurrentIndex(ind) self.currentWidget().setFocus() def nextTab(self): """ Public slot used to show the next tab. """ ind = self.currentIndex() + 1 if ind == self.count(): ind = 0 self.setCurrentIndex(ind) self.currentWidget().setFocus() def count(self): """ Public method to get the number of tabs. @return number of tabs in the sidebar (integer) """ return self.__tabBar.count() def currentIndex(self): """ Public method to get the index of the current tab. @return index of the current tab (integer) """ return self.__stackedWidget.currentIndex() def setCurrentIndex(self, index): """ Public slot to set the current index. @param index the index to set as the current index (integer) """ self.__tabBar.setCurrentIndex(index) self.__stackedWidget.setCurrentIndex(index) if self.isMinimized(): self.expand() def currentWidget(self): """ Public method to get a reference to the current widget. @return reference to the current widget (QWidget) """ return self.__stackedWidget.currentWidget() def setCurrentWidget(self, widget): """ Public slot to set the current widget. @param widget reference to the widget to become the current widget (QWidget) """ self.__stackedWidget.setCurrentWidget(widget) self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex()) if self.isMinimized(): self.expand() def indexOf(self, widget): """ Public method to get the index of the given widget. @param widget reference to the widget to get the index of (QWidget) @return index of the given widget (integer) """ return self.__stackedWidget.indexOf(widget) def isTabEnabled(self, index): """ Public method to check, if a tab is enabled. @param index index of the tab to check (integer) @return flag indicating the enabled state (boolean) """ return self.__tabBar.isTabEnabled(index) def setTabEnabled(self, index, enabled): """ Public method to set the enabled state of a tab. @param index index of the tab to set (integer) @param enabled enabled state to set (boolean) """ self.__tabBar.setTabEnabled(index, enabled) def orientation(self): """ Public method to get the orientation of the sidebar. @return orientation of the sidebar (North, East, South, West) """ return self.__orientation def setOrientation(self, orient): """ Public method to set the orientation of the sidebar. @param orient orientation of the sidebar (North, East, South, West) """ if orient == E5SideBar.North: self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.TopToBottom) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.East: self.__tabBar.setShape(QTabBar.RoundedEast) self.__tabBar.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.RightToLeft) self.layout.setAlignment(self.barLayout, Qt.AlignTop) elif orient == E5SideBar.South: self.__tabBar.setShape(QTabBar.RoundedSouth) self.__tabBar.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.BottomToTop) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.West: self.__tabBar.setShape(QTabBar.RoundedWest) self.__tabBar.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.LeftToRight) self.layout.setAlignment(self.barLayout, Qt.AlignTop) self.__orientation = orient def tabIcon(self, index): """ Public method to get the icon of a tab. @param index index of the tab (integer) @return icon of the tab (QIcon) """ return self.__tabBar.tabIcon(index) def setTabIcon(self, index, icon): """ Public method to set the icon of a tab. @param index index of the tab (integer) @param icon icon to be set (QIcon) """ self.__tabBar.setTabIcon(index, icon) def tabText(self, index): """ Public method to get the text of a tab. @param index index of the tab (integer) @return text of the tab (string) """ return self.__tabBar.tabText(index) def setTabText(self, index, text): """ Public method to set the text of a tab. @param index index of the tab (integer) @param text text to set (string) """ self.__tabBar.setTabText(index, text) def tabToolTip(self, index): """ Public method to get the tooltip text of a tab. @param index index of the tab (integer) @return tooltip text of the tab (string) """ return self.__tabBar.tabToolTip(index) def setTabToolTip(self, index, tip): """ Public method to set the tooltip text of a tab. @param index index of the tab (integer) @param tip tooltip text to set (string) """ self.__tabBar.setTabToolTip(index, tip) def tabWhatsThis(self, index): """ Public method to get the WhatsThis text of a tab. @param index index of the tab (integer) @return WhatsThis text of the tab (string) """ return self.__tabBar.tabWhatsThis(index) def setTabWhatsThis(self, index, text): """ Public method to set the WhatsThis text of a tab. @param index index of the tab (integer) @param text WhatsThis text to set (string) """ self.__tabBar.setTabWhatsThis(index, text) def widget(self, index): """ Public method to get a reference to the widget associated with a tab. @param index index of the tab (integer) @return reference to the widget (QWidget) """ return self.__stackedWidget.widget(index) def saveState(self): """ Public method to save the state of the sidebar. @return saved state as a byte array (QByteArray) """ if len(self.splitterSizes) == 0: if self.splitter: self.splitterSizes = self.splitter.sizes() self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) stream.writeUInt16(self.Version) stream.writeBool(self.__minimized) stream << self.__bigSize stream.writeUInt16(self.__minSize) stream.writeUInt16(self.__maxSize) stream.writeUInt16(len(self.splitterSizes)) for size in self.splitterSizes: stream.writeUInt16(size) stream.writeBool(self.__autoHide) return data def restoreState(self, state): """ Public method to restore the state of the sidebar. @param state byte array containing the saved state (QByteArray) @return flag indicating success (boolean) """ if state.isEmpty(): return False if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = self.layout.minimumSize().height() maxSize = self.maximumHeight() else: minSize = self.layout.minimumSize().width() maxSize = self.maximumWidth() data = QByteArray(state) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) stream.readUInt16() # version minimized = stream.readBool() if minimized and not self.__minimized: self.shrink() stream >> self.__bigSize self.__minSize = max(stream.readUInt16(), minSize) self.__maxSize = max(stream.readUInt16(), maxSize) count = stream.readUInt16() self.splitterSizes = [] for i in range(count): self.splitterSizes.append(stream.readUInt16()) self.__autoHide = stream.readBool() self.__autoHideButton.setChecked(not self.__autoHide) if not minimized: self.expand() return True ####################################################################### ## methods below implement the autohide functionality ####################################################################### def __autoHideToggled(self, checked): """ Private slot to handle the toggling of the autohide button. @param checked flag indicating the checked state of the button (boolean) """ self.__autoHide = not checked if self.__autoHide: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOn.png")) else: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) def __appFocusChanged(self, old, now): """ Private slot to handle a change of the focus. @param old reference to the widget, that lost focus (QWidget or None) @param now reference to the widget having the focus (QWidget or None) """ self.__hasFocus = self.isAncestorOf(now) if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() elif self.__autoHide and self.__hasFocus and self.isMinimized(): self.expand() def enterEvent(self, event): """ Protected method to handle the mouse entering this widget. @param event reference to the event (QEvent) """ if self.__autoHide and self.isMinimized(): self.expand() else: self.__cancelDelayTimer() def leaveEvent(self, event): """ Protected method to handle the mouse leaving this widget. @param event reference to the event (QEvent) """ if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() else: self.__cancelDelayTimer() def shutdown(self): """ Public method to shut down the object. This method does some preparations so the object can be deleted properly. It disconnects from the focusChanged signal in order to avoid trouble later on. """ e5App().focusChanged[QWidget, QWidget].disconnect( self.__appFocusChanged)
# add buttons btn1 = QPushButton("PLAY") player = QMediaPlayer() media = QMediaContent(QUrl(sys.argv[1])) player.setMedia(media) # Create and set video output widget video = ClickableVideoWidget() player.setVideoOutput(video) # add to stacked layout print("Added widget", w.addWidget(btn1)) print("Added widget", w.addWidget(video)) def play_video(): w.setCurrentIndex(1) player.play() def pause_video(): player.pause() w.setCurrentIndex(0) # connect signals to stack change layout slot btn1.clicked.connect(play_video) video.clicked.connect(pause_video) w.show() sys.exit(app.exec_())
class Toolbox(QWidget): """A side-oriented widget similar to a TabWidget that can be collapsed and expanded. A Toolbox is designed to be a container for sets of controls, grouped into 'pages' and accessible by a TabBar, in the same way as a TabWidget. A page is normally a QWidget with a layout that contains controls. A widget can be added as a new tab using :meth:`addTab`. The Toolbox has slots for triggering its collapse and expansion, both in an animated mode (soft slide) and a 'quick' mode which skips the animation. Commonly the collapse/expand slots are connected to the tabBar's :meth:`tabBarDoubleClicked` signal. Normally in the DataLogger a Toolbox is created and then added to a :class:`~cued_datalogger.api.toolbox.MasterToolbox`, which connects the relevant signals for collapsing and expanding the Toolbox. Attributes ---------- tabBar : QTabBar tabPages : QStackedWidget The stack of widgets that form the pages of the tabs. collapse_animation : QPropertyAnimation The animation that controls how the Toolbox collapses. """ sig_collapsed_changed = pyqtSignal() def __init__(self, widget_side='left', parent=None): self.parent = parent self.widget_side = widget_side super().__init__(parent) self.layout = QHBoxLayout() self.layout.setSpacing(0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # # Create the tab bar self.tabBar = QTabBar(self) self.tabBar.setTabsClosable(False) self.tabBar.setMovable(False) self.tabBar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding) # # Create the Stacked widget for the pages self.tabPages = QStackedWidget(self) # # Link the signals so that changing tab leads to a change of page self.tabBar.currentChanged.connect(self.changePage) # # Add them to the splitter (self) # Right side orientation if self.widget_side == 'right': self.tabBar.setShape(QTabBar.RoundedWest) self.layout.addWidget(self.tabBar) self.layout.addWidget(self.tabPages) # Left side orientation else: self.tabBar.setShape(QTabBar.RoundedEast) self.layout.addWidget(self.tabPages) self.layout.addWidget(self.tabBar) self.setLayout(self.layout) self.collapsed = False self.expanded_width = self.sizeHint().width() def addTab(self, widget, title): """Add a new tab, with the page widget *widget* and tab title *title*.""" self.tabBar.addTab(title) self.tabPages.addWidget(widget) def removeTab(self, title): """Remove the tab with title *title*.""" for tab_num in range(self.tabBar.count()): if self.tabBar.tabText(tab_num) == title: self.tabBar.removeTab(tab_num) self.tabPages.removeWidget(self.tabPages.widget(tab_num)) def toggle_collapse(self): """If collapsed, expand the widget so the pages are visible. If not collapsed, collapse the widget so that only the tabBar is showing.""" # If collapsed, expand if self.collapsed: self.expand() # If expanded, collapse: else: self.collapse() def expand(self): """Expand the widget so that the pages are visible.""" self.tabPages.show() self.sig_collapsed_changed.emit() self.collapsed = False def collapse(self): """Collapse the widget so that only the tab bar is visible.""" self.tabPages.hide() self.sig_collapsed_changed.emit() self.collapsed = True def changePage(self, index): """Set the current page to *index*.""" self.tabBar.setCurrentIndex(index) self.tabPages.setCurrentIndex(index) if self.tabPages.currentWidget(): self.tabPages.currentWidget().resize(self.tabPages.size()) def clear(self): """Remove all tabs and pages.""" for i in range(self.tabBar.count()): # Remove the tab and page at position 0 self.tabBar.removeTab(0) self.tabPages.removeWidget(self.tabPages.currentWidget())
super(Downloader, self).__init__() self.title = title self.url = url self.extension = extension def run(self): book_module.downloadBook(self.title, self.url, self.extension) if __name__ == '__main__': app = QApplication(sys.argv) bookmark_module.makeFiles() main_win = MainWin() search = Search() bookmarks = Bookmarks() book_page = bookPage() stackedWidget = QStackedWidget() stackedWidget.addWidget(main_win) stackedWidget.addWidget(search) stackedWidget.addWidget(bookmarks) stackedWidget.addWidget(book_page) stackedWidget.setFixedSize(712, 403) stackedWidget.setStyleSheet("background-color: rgb(54, 54, 54); " "color: rgb(255, 255, 255);") stackedWidget.show() sys.exit(app.exec_())
class MainWindow(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.settings = QSettings() self.is_visible = True self.is_expanded = True self.is_move_action = False self.should_confirm_close = False self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_NoSystemBackground, True) self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_QuitOnClose) self.setWindowIcon(QIcon(join(config.ASSETS_DIR, "droptopus.png"))) self.setWindowTitle("Droptopus") self.miniwin = MiniWindow(self) self.frame = DropFrame(self) self.frame.show() self.readSettings() self.content = QStackedWidget() self.setCentralWidget(self.content) self.content.addWidget(self.frame) self.content.addWidget(self.miniwin) self.setAcceptDrops(True) self.setMouseTracking(True) self.collapse() def contextMenuEvent(self, event): menu = QMenu(self) label = ("Expand", "Collapse")[self.is_expanded] expand_action = menu.addAction(label) about_action = menu.addAction("About") menu.addSeparator() quit_action = menu.addAction("Quit") action = menu.exec_(self.mapToGlobal(event.pos())) if action == expand_action: if self.is_expanded: self.collapse() else: self.expand() elif action == about_action: self.showAbout() elif action == quit_action: self.should_confirm_close = True self.close() def expand(self): if self.is_expanded: return self.is_expanded = True self.setAcceptDrops(False) self.content.hide() expanded = self.frame.sizeHint() self.setMinimumSize(expanded) self.content.setCurrentWidget(self.frame) self.content.show() self.resize(expanded) # on OSX the window will not automatically stay inside the screen like on Linux # we have to do it manually screen_rect = QDesktopWidget().screenGeometry() window_rect = self.frameGeometry() intersection = window_rect & screen_rect dx = window_rect.width() - intersection.width() dy = window_rect.height() - intersection.height() unseen = window_rect & intersection if dx != 0 or dy != 0: if window_rect.left() > screen_rect.left(): dx = dx * -1 if window_rect.bottom() > screen_rect.bottom(): dy = dy * -1 self.move(window_rect.left() + dx, window_rect.top() + dy) def collapse(self): if not self.is_expanded: return self.is_expanded = False self.setAcceptDrops(True) self.content.hide() mini = self.miniwin.sizeHint() self.setMinimumSize(mini) self.move(self.anchor) self.content.setCurrentWidget(self.miniwin) self.content.show() self.resize(mini) def showAbout(self): about = AboutDialog(self) about.setModal(True) about.show() def mouseReleaseEvent(self, event): self.is_move_action = False def mousePressEvent(self, event): if not event.button() == Qt.LeftButton: return self.is_move_action = True self.offset = event.pos() def mouseMoveEvent(self, event): if not self.is_move_action: return x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x - x_w, y - y_w) if self.content.currentWidget() == self.miniwin: self.anchor = self.pos() def writeSettings(self): self.settings.beginGroup("MainWindow") self.settings.setValue("anchor", self.anchor) self.settings.endGroup() def readSettings(self): self.settings.beginGroup("MainWindow") saved_anchor = self.settings.value("anchor", None) if saved_anchor != None: self.anchor = saved_anchor else: rect = QDesktopWidget().screenGeometry() mini = self.miniwin.sizeHint() self.anchor = QPoint(rect.right() - mini.width(), rect.bottom() - mini.height()) self.settings.endGroup() def userReallyWantsToQuit(self): if not self.should_confirm_close: return True reply = QMessageBox.question( self, "Close Droptopus", "Are you sure you want to close the application?", QMessageBox.Yes, QMessageBox.No, ) return reply == QMessageBox.Yes def closeEvent(self, event): if self.userReallyWantsToQuit(): self.writeSettings() event.accept() else: event.ignore() # def dragMoveEvent(self, event): # super(MainWindow, self).dragMoveEvent(event) def dragEnterEvent(self, event): if not self.is_expanded: QTimer.singleShot(200, self.expand) else: super(MainWindow, self).dragEnterEvent(event) def mouseDoubleClickEvent(self, event): if self.is_expanded: self.collapse() else: self.expand() def event(self, evt): et = evt.type() if et == events.COLLAPSE_WINDOW: evt.accept() self.collapse() return True if evt.type() == events.RELOAD_WIDGETS: evt.accept() if self.is_expanded: self.resize(self.sizeHint()) if et == events.EXPAND_WINDOW: evt.accept() self.expand() return True elif et == events.CLOSE_WINDOW: evt.accept() self.should_confirm_close = True self.close() return True return super(MainWindow, self).event(evt)
class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(800, 600) # window = QMainWindow() # window.resize(800, 600) # self.centralwidget = QtWidgets.QWidget(MainWindow) # self.centralwidget = QtWidgets.QWidget(MainWindow) self.stack = QStackedWidget(parent=MainWindow) self.centralwidget = QStackedWidget(parent=MainWindow) self.centralwidget.setObjectName("centralwidget") # self.matplotlibwidget_static = MatplotlibWidget(self.centralwidget) matplotlibwidget_static = MatplotlibWidget() matplotlibwidget_static.setGeometry(QtCore.QRect(10, 0, 611, 271)) matplotlibwidget_static.setObjectName("matplotlibwidget_static") self.stack.addWidget(matplotlibwidget_static) self.pushButton = QtWidgets.QPushButton(self.centralwidget) self.pushButton.setGeometry(QtCore.QRect(670, 80, 75, 23)) self.pushButton.setObjectName("pushButton") # self.matplotlibwidget_dynamic = MatplotlibWidget() # self.matplotlibwidget_dynamic.setEnabled(True) # self.matplotlibwidget_dynamic.setGeometry(QtCore.QRect(10, 270, 611, 291)) # self.matplotlibwidget_dynamic.setObjectName("matplotlibwidget_dynamic") matplotlibwidget_dynamic = MatplotlibWidget() matplotlibwidget_dynamic.setEnabled(True) matplotlibwidget_dynamic.setGeometry(QtCore.QRect(10, 0, 611, 271)) matplotlibwidget_dynamic.setObjectName("matplotlibwidget_dynamic") self.stack.addWidget(matplotlibwidget_dynamic) self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget) self.pushButton_2.setGeometry(QtCore.QRect(670, 370, 75, 23)) self.pushButton_2.setObjectName("pushButton_2") MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.stack.show() # window.show() def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.pushButton.setText(_translate("MainWindow", "显示静态图")) self.pushButton_2.setText(_translate("MainWindow", "显示动态图")) def next(self): self.stack.setCurrentIndex(self.stack.currentIndex() + 1) print('current', self.stack.currentIndex())
class MainController: palette: QPalette _welcome_gui: QStackedWidget _welcome_ui: Ui_Welcome _dcm_gui: QMainWindow _dcm_ui: Ui_MainWindow _about_gui: QDialog _about_ui: Ui_aboutWindow _about_table: QTableWidget _about_header: Dict[str, str] _params_gui: QDialog _params_ui: Ui_parametersWindow _reports_gui: QDialog _reports_ui: Ui_ReportsWindow _egram_report_gui: QDialog _egram_report_ui: Ui_Dialog _auth: AuthHandler _conn: ConnectionHandler _params: ParametersHandler _reports: ReportsHandler _graphs: GraphsHandler def __init__(self): # Set theme to dark mode self.palette = QPalette() self.palette.setColor(QPalette.Window, QColor(53, 53, 53)) self.palette.setColor(QPalette.WindowText, Qt.white) # Setup welcome screen UI from auto-generated file self._welcome_gui = QStackedWidget() self._welcome_ui = welcomescreen.Ui_Welcome() self._welcome_ui.setupUi(self._welcome_gui) # Setup dcm screen UI from auto-generated file self._dcm_gui = QMainWindow() self._dcm_ui = dcm.Ui_MainWindow() self._dcm_ui.setupUi(self._dcm_gui) for i, button in enumerate(self._dcm_ui.pacing_mode_group.buttons()): self._dcm_ui.pacing_mode_group.setId(button, i) # Setup about screen UI from auto-generated file self._about_gui = QDialog() self._about_ui = about.Ui_aboutWindow() self._about_ui.setupUi(self._about_gui) self._about_table = self._about_ui.tableWidget self._about_header = { self._about_table.verticalHeaderItem(row).text(): self._about_table.item(row, 0).text() for row in range(self._about_table.rowCount()) } # Setup parameter screen UI from auto-generated file self._params_gui = QDialog() self._params_ui = parameters.Ui_parametersWindow() self._params_ui.setupUi(self._params_gui) # Setup reports screen UI from auto-generated file self._reports_gui = QDialog() self._reports_ui = reports.Ui_ReportsWindow() self._reports_ui.setupUi(self._reports_gui) # Setup egram screen UI from auto-generated file self._egram_report_gui = QDialog() self._egram_report_ui = egram_report.Ui_Dialog() self._egram_report_ui.setupUi(self._egram_report_gui) # Initialize separate handlers for authentication, pacemaker connection, parameters, reports and graphs self._auth = AuthHandler(self.show_dcm) self._conn = ConnectionHandler() self._params = ParametersHandler(self._params_ui.tableWidget) self._reports = ReportsHandler(self._egram_report_ui) self._graphs = GraphsHandler(self._dcm_ui.atrial_plots, self._dcm_ui.vent_plots, data_size=2001) # Link elements to actions self.link_welcome_buttons() self.link_dcm_elements() self.link_reports_buttons() self.link_params_buttons() # Start connection thread self._conn.connect_status_change.connect(self.handle_pace_conn) self._conn.serial.ecg_data_update.connect(self._graphs.update_data) self._conn.serial.params_received.connect(self._show_alert) self._conn.start() # Update params GUI table to show default pacing mode params self._params.update_row_visibility( self._dcm_ui.pacing_mode_group.checkedButton().text()) # Show welcome screen GUI self._welcome_gui.show() # Link welcome screen ui elements to their respective functions def link_welcome_buttons(self) -> None: # Welcome screen # show register and login screens when those buttons are pressed, respectively self._welcome_ui.reg_btn.clicked.connect( lambda: self._welcome_gui.setCurrentIndex(1)) self._welcome_ui.log_btn.clicked.connect( lambda: self._welcome_gui.setCurrentIndex(2)) # Register screen # register user and go back to welcome screen when those buttons are pressed, respectively self._welcome_ui.reg_submit_btn.clicked.connect( lambda: self._auth.register(self._welcome_ui.reg_user.text(), self._welcome_ui.reg_pass.text())) self._welcome_ui.reg_back_btn.clicked.connect( lambda: self._welcome_gui.setCurrentIndex(0)) # Login screen # login user and go back to welcome screen when those buttons are pressed, respectively self._welcome_ui.log_submit_btn.clicked.connect( lambda: self._auth.login(self._welcome_ui.log_user.text(), self._welcome_ui.log_pass.text())) self._welcome_ui.log_back_btn.clicked.connect( lambda: self._welcome_gui.setCurrentIndex(0)) # Link dcm ui elements to their respective functions def link_dcm_elements(self) -> None: # Buttons self._dcm_ui.quit_btn.clicked.connect( self._dcm_gui.close ) # close dcm and quit program when quit is pressed self._dcm_ui.about_btn.clicked.connect( self._about_gui.exec_) # show about screen when about is pressed self._dcm_ui.parameters_btn.clicked.connect( self._params_gui.exec_ ) # show params screen when params is pressed self._dcm_ui.reports_btn.clicked.connect( self._reports_gui.exec_ ) # show reports screen when reports is pressed self._dcm_ui.new_patient_btn.clicked.connect( self._conn.register_device) # register pacemaker on btn press # write serial data when btn is pressed self._dcm_ui.pace_btn.clicked.connect( lambda: self._conn.send_data_to_pacemaker( self._params.get_params(self.get_current_pace_index()))) # update the params GUI table to only show the params for the current pacing mode self._dcm_ui.pacing_mode_group.buttonClicked.connect( lambda: self._params.update_row_visibility( self.get_current_pace_mode())) # Checkboxes # show or hide the plots, depending on whether or not the checkbox is checked, when it changes state self._dcm_ui.atrial_box.stateChanged.connect( lambda: self._graphs.atri_vis(self._dcm_ui.atrial_box.isChecked())) self._dcm_ui.vent_box.stateChanged.connect( lambda: self._graphs.vent_vis(self._dcm_ui.vent_box.isChecked())) # Link reports ui elements to their respective functions def link_reports_buttons(self) -> None: # Get the params based on the pacing mode, and then generate the respective report based on the pressed btn self._reports_ui.egram_btn.clicked.connect(self.show_egram_report) self._reports_ui.brady_btn.clicked.connect( lambda: self._reports.generate_brady(self._about_header, self.get_pace_mode_params())) # Get the params based on the pacing mode, and then generate the respective report based on the pressed btn self._egram_report_ui.export_btn.clicked.connect( lambda: self._reports.export_pdf(self._egram_report_gui)) # Link parameters ui elements to their respective functions def link_params_buttons(self) -> None: self._params_ui.confirm_btn.clicked.connect( self._params.confirm ) # update stored params and write them to disk self._params_ui.reset_btn.clicked.connect( self._params.reset ) # reset stored and shown params to GUI defaults # Upon successful user registration or login, close the welcome screen, show the dcm and load params for user def show_dcm(self, username: str) -> None: self._welcome_gui.close() self._dcm_gui.show() self._params.update_params_on_user_auth(username) # Upon successful user registration or login, close the welcome screen, show the dcm and load params for user def show_egram_report(self) -> None: self._reports.generate_egram(self._about_header, self._dcm_ui.atrial_plots.grab(), self._dcm_ui.vent_plots.grab()) self._egram_report_gui.exec_() # Upon successful pacemaker connection, update the status bar animation and the About window table def handle_pace_conn(self, conn_state: PacemakerState, msg: str) -> None: self._dcm_ui.statusbar.handle_conn_anim(conn_state, msg) self._about_header[ "Device serial number"] = msg if conn_state != PacemakerState.NOT_CONNECTED else "None" for row in range(self._about_table.rowCount()): self._about_table.item(row, 0).setText(self._about_header[ self._about_table.verticalHeaderItem(row).text()]) # Get only the parameters required for the current pacing mode def get_pace_mode_params(self) -> Dict[str, str]: return self._params.filter_params(self.get_current_pace_mode()) # Get current pacing mode index in button group def get_current_pace_index(self) -> int: return self._dcm_ui.pacing_mode_group.checkedId() # Get current pacing mode name def get_current_pace_mode(self) -> str: return self._dcm_ui.pacing_mode_group.checkedButton().text() @staticmethod def _show_alert(success: bool, msg: str) -> None: """ Displays an error message with the specified text :param msg: the text to show """ qm = QMessageBox() if success: QMessageBox.information(qm, "Connection", msg, QMessageBox.Ok, QMessageBox.Ok) else: QMessageBox.critical(qm, "Connection", msg, QMessageBox.Ok, QMessageBox.Ok) # Stop threads spawned by handlers def stop_threads(self): self._conn.stop()
class E5SideBar(QWidget): """ Class implementing a sidebar with a widget area, that is hidden or shown, if the current tab is clicked again. """ Version = 1 North = 0 East = 1 South = 2 West = 3 def __init__(self, orientation=None, delay=200, parent=None): """ Constructor @param orientation orientation of the sidebar widget (North, East, South, West) @param delay value for the expand/shrink delay in milliseconds (integer) @param parent parent widget (QWidget) """ super(E5SideBar, self).__init__(parent) self.__tabBar = QTabBar() self.__tabBar.setDrawBase(True) self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setUsesScrollButtons(True) self.__tabBar.setDrawBase(False) self.__stackedWidget = QStackedWidget(self) self.__stackedWidget.setContentsMargins(0, 0, 0, 0) self.__autoHideButton = QToolButton() self.__autoHideButton.setCheckable(True) self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) self.__autoHideButton.setChecked(True) self.__autoHideButton.setToolTip( self.tr("Deselect to activate automatic collapsing")) self.barLayout = QBoxLayout(QBoxLayout.LeftToRight) self.barLayout.setContentsMargins(0, 0, 0, 0) self.layout = QBoxLayout(QBoxLayout.TopToBottom) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) self.barLayout.addWidget(self.__autoHideButton) self.barLayout.addWidget(self.__tabBar) self.layout.addLayout(self.barLayout) self.layout.addWidget(self.__stackedWidget) self.setLayout(self.layout) # initialize the delay timer self.__actionMethod = None self.__delayTimer = QTimer(self) self.__delayTimer.setSingleShot(True) self.__delayTimer.setInterval(delay) self.__delayTimer.timeout.connect(self.__delayedAction) self.__minimized = False self.__minSize = 0 self.__maxSize = 0 self.__bigSize = QSize() self.splitter = None self.splitterSizes = [] self.__hasFocus = False # flag storing if this widget or any child has the focus self.__autoHide = False self.__tabBar.installEventFilter(self) self.__orientation = E5SideBar.North if orientation is None: orientation = E5SideBar.North self.setOrientation(orientation) self.__tabBar.currentChanged[int].connect( self.__stackedWidget.setCurrentIndex) e5App().focusChanged.connect(self.__appFocusChanged) self.__autoHideButton.toggled[bool].connect(self.__autoHideToggled) def setSplitter(self, splitter): """ Public method to set the splitter managing the sidebar. @param splitter reference to the splitter (QSplitter) """ self.splitter = splitter self.splitter.splitterMoved.connect(self.__splitterMoved) self.splitter.setChildrenCollapsible(False) index = self.splitter.indexOf(self) self.splitter.setCollapsible(index, False) def __splitterMoved(self, pos, index): """ Private slot to react on splitter moves. @param pos new position of the splitter handle (integer) @param index index of the splitter handle (integer) """ if self.splitter: self.splitterSizes = self.splitter.sizes() def __delayedAction(self): """ Private slot to handle the firing of the delay timer. """ if self.__actionMethod is not None: self.__actionMethod() def setDelay(self, delay): """ Public method to set the delay value for the expand/shrink delay in milliseconds. @param delay value for the expand/shrink delay in milliseconds (integer) """ self.__delayTimer.setInterval(delay) def delay(self): """ Public method to get the delay value for the expand/shrink delay in milliseconds. @return value for the expand/shrink delay in milliseconds (integer) """ return self.__delayTimer.interval() def __cancelDelayTimer(self): """ Private method to cancel the current delay timer. """ self.__delayTimer.stop() self.__actionMethod = None def shrink(self): """ Public method to record a shrink request. """ self.__delayTimer.stop() self.__actionMethod = self.__shrinkIt self.__delayTimer.start() def __shrinkIt(self): """ Private method to shrink the sidebar. """ self.__minimized = True self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() if self.splitter: self.splitterSizes = self.splitter.sizes() self.__stackedWidget.hide() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.setFixedHeight(self.__tabBar.minimumSizeHint().height()) else: self.setFixedWidth(self.__tabBar.minimumSizeHint().width()) self.__actionMethod = None def expand(self): """ Public method to record a expand request. """ self.__delayTimer.stop() self.__actionMethod = self.__expandIt self.__delayTimer.start() def __expandIt(self): """ Private method to expand the sidebar. """ self.__minimized = False self.__stackedWidget.show() self.resize(self.__bigSize) if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = max(self.__minSize, self.minimumSizeHint().height()) self.setMinimumHeight(minSize) self.setMaximumHeight(self.__maxSize) else: minSize = max(self.__minSize, self.minimumSizeHint().width()) self.setMinimumWidth(minSize) self.setMaximumWidth(self.__maxSize) if self.splitter: self.splitter.setSizes(self.splitterSizes) self.__actionMethod = None def isMinimized(self): """ Public method to check the minimized state. @return flag indicating the minimized state (boolean) """ return self.__minimized def isAutoHiding(self): """ Public method to check, if the auto hide function is active. @return flag indicating the state of auto hiding (boolean) """ return self.__autoHide def eventFilter(self, obj, evt): """ Public method to handle some events for the tabbar. @param obj reference to the object (QObject) @param evt reference to the event object (QEvent) @return flag indicating, if the event was handled (boolean) """ if obj == self.__tabBar: if evt.type() == QEvent.MouseButtonPress: pos = evt.pos() for i in range(self.__tabBar.count()): if self.__tabBar.tabRect(i).contains(pos): break if i == self.__tabBar.currentIndex(): if self.isMinimized(): self.expand() else: self.shrink() return True elif self.isMinimized(): self.expand() elif evt.type() == QEvent.Wheel: if qVersion() >= "5.0.0": delta = evt.angleDelta().y() else: delta = evt.delta() if delta > 0: self.prevTab() else: self.nextTab() return True return QWidget.eventFilter(self, obj, evt) def addTab(self, widget, iconOrLabel, label=None): """ Public method to add a tab to the sidebar. @param widget reference to the widget to add (QWidget) @param iconOrLabel reference to the icon or the label text of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.addTab(iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.addTab(iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.addWidget(widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def insertTab(self, index, widget, iconOrLabel, label=None): """ Public method to insert a tab into the sidebar. @param index the index to insert the tab at (integer) @param widget reference to the widget to insert (QWidget) @param iconOrLabel reference to the icon or the labeltext of the tab (QIcon, string) @param label the labeltext of the tab (string) (only to be used, if the second parameter is a QIcon) """ if label: index = self.__tabBar.insertTab(index, iconOrLabel, label) self.__tabBar.setTabToolTip(index, label) else: index = self.__tabBar.insertTab(index, iconOrLabel) self.__tabBar.setTabToolTip(index, iconOrLabel) self.__stackedWidget.insertWidget(index, widget) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def removeTab(self, index): """ Public method to remove a tab. @param index the index of the tab to remove (integer) """ self.__stackedWidget.removeWidget(self.__stackedWidget.widget(index)) self.__tabBar.removeTab(index) if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() else: self.__minSize = self.minimumSizeHint().width() def clear(self): """ Public method to remove all tabs. """ while self.count() > 0: self.removeTab(0) def prevTab(self): """ Public slot used to show the previous tab. """ ind = self.currentIndex() - 1 if ind == -1: ind = self.count() - 1 self.setCurrentIndex(ind) self.currentWidget().setFocus() def nextTab(self): """ Public slot used to show the next tab. """ ind = self.currentIndex() + 1 if ind == self.count(): ind = 0 self.setCurrentIndex(ind) self.currentWidget().setFocus() def count(self): """ Public method to get the number of tabs. @return number of tabs in the sidebar (integer) """ return self.__tabBar.count() def currentIndex(self): """ Public method to get the index of the current tab. @return index of the current tab (integer) """ return self.__stackedWidget.currentIndex() def setCurrentIndex(self, index): """ Public slot to set the current index. @param index the index to set as the current index (integer) """ self.__tabBar.setCurrentIndex(index) self.__stackedWidget.setCurrentIndex(index) if self.isMinimized(): self.expand() def currentWidget(self): """ Public method to get a reference to the current widget. @return reference to the current widget (QWidget) """ return self.__stackedWidget.currentWidget() def setCurrentWidget(self, widget): """ Public slot to set the current widget. @param widget reference to the widget to become the current widget (QWidget) """ self.__stackedWidget.setCurrentWidget(widget) self.__tabBar.setCurrentIndex(self.__stackedWidget.currentIndex()) if self.isMinimized(): self.expand() def indexOf(self, widget): """ Public method to get the index of the given widget. @param widget reference to the widget to get the index of (QWidget) @return index of the given widget (integer) """ return self.__stackedWidget.indexOf(widget) def isTabEnabled(self, index): """ Public method to check, if a tab is enabled. @param index index of the tab to check (integer) @return flag indicating the enabled state (boolean) """ return self.__tabBar.isTabEnabled(index) def setTabEnabled(self, index, enabled): """ Public method to set the enabled state of a tab. @param index index of the tab to set (integer) @param enabled enabled state to set (boolean) """ self.__tabBar.setTabEnabled(index, enabled) def orientation(self): """ Public method to get the orientation of the sidebar. @return orientation of the sidebar (North, East, South, West) """ return self.__orientation def setOrientation(self, orient): """ Public method to set the orientation of the sidebar. @param orient orientation of the sidebar (North, East, South, West) """ if orient == E5SideBar.North: self.__tabBar.setShape(QTabBar.RoundedNorth) self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.TopToBottom) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.East: self.__tabBar.setShape(QTabBar.RoundedEast) self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.RightToLeft) self.layout.setAlignment(self.barLayout, Qt.AlignTop) elif orient == E5SideBar.South: self.__tabBar.setShape(QTabBar.RoundedSouth) self.__tabBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.barLayout.setDirection(QBoxLayout.LeftToRight) self.layout.setDirection(QBoxLayout.BottomToTop) self.layout.setAlignment(self.barLayout, Qt.AlignLeft) elif orient == E5SideBar.West: self.__tabBar.setShape(QTabBar.RoundedWest) self.__tabBar.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.barLayout.setDirection(QBoxLayout.TopToBottom) self.layout.setDirection(QBoxLayout.LeftToRight) self.layout.setAlignment(self.barLayout, Qt.AlignTop) self.__orientation = orient def tabIcon(self, index): """ Public method to get the icon of a tab. @param index index of the tab (integer) @return icon of the tab (QIcon) """ return self.__tabBar.tabIcon(index) def setTabIcon(self, index, icon): """ Public method to set the icon of a tab. @param index index of the tab (integer) @param icon icon to be set (QIcon) """ self.__tabBar.setTabIcon(index, icon) def tabText(self, index): """ Public method to get the text of a tab. @param index index of the tab (integer) @return text of the tab (string) """ return self.__tabBar.tabText(index) def setTabText(self, index, text): """ Public method to set the text of a tab. @param index index of the tab (integer) @param text text to set (string) """ self.__tabBar.setTabText(index, text) def tabToolTip(self, index): """ Public method to get the tooltip text of a tab. @param index index of the tab (integer) @return tooltip text of the tab (string) """ return self.__tabBar.tabToolTip(index) def setTabToolTip(self, index, tip): """ Public method to set the tooltip text of a tab. @param index index of the tab (integer) @param tip tooltip text to set (string) """ self.__tabBar.setTabToolTip(index, tip) def tabWhatsThis(self, index): """ Public method to get the WhatsThis text of a tab. @param index index of the tab (integer) @return WhatsThis text of the tab (string) """ return self.__tabBar.tabWhatsThis(index) def setTabWhatsThis(self, index, text): """ Public method to set the WhatsThis text of a tab. @param index index of the tab (integer) @param text WhatsThis text to set (string) """ self.__tabBar.setTabWhatsThis(index, text) def widget(self, index): """ Public method to get a reference to the widget associated with a tab. @param index index of the tab (integer) @return reference to the widget (QWidget) """ return self.__stackedWidget.widget(index) def saveState(self): """ Public method to save the state of the sidebar. @return saved state as a byte array (QByteArray) """ if len(self.splitterSizes) == 0: if self.splitter: self.splitterSizes = self.splitter.sizes() self.__bigSize = self.size() if self.__orientation in [E5SideBar.North, E5SideBar.South]: self.__minSize = self.minimumSizeHint().height() self.__maxSize = self.maximumHeight() else: self.__minSize = self.minimumSizeHint().width() self.__maxSize = self.maximumWidth() data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) stream.writeUInt16(self.Version) stream.writeBool(self.__minimized) stream << self.__bigSize stream.writeUInt16(self.__minSize) stream.writeUInt16(self.__maxSize) stream.writeUInt16(len(self.splitterSizes)) for size in self.splitterSizes: stream.writeUInt16(size) stream.writeBool(self.__autoHide) return data def restoreState(self, state): """ Public method to restore the state of the sidebar. @param state byte array containing the saved state (QByteArray) @return flag indicating success (boolean) """ if state.isEmpty(): return False if self.__orientation in [E5SideBar.North, E5SideBar.South]: minSize = self.layout.minimumSize().height() maxSize = self.maximumHeight() else: minSize = self.layout.minimumSize().width() maxSize = self.maximumWidth() data = QByteArray(state) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) stream.readUInt16() # version minimized = stream.readBool() if minimized and not self.__minimized: self.shrink() stream >> self.__bigSize self.__minSize = max(stream.readUInt16(), minSize) self.__maxSize = max(stream.readUInt16(), maxSize) count = stream.readUInt16() self.splitterSizes = [] for i in range(count): self.splitterSizes.append(stream.readUInt16()) self.__autoHide = stream.readBool() self.__autoHideButton.setChecked(not self.__autoHide) if not minimized: self.expand() return True ####################################################################### ## methods below implement the autohide functionality ####################################################################### def __autoHideToggled(self, checked): """ Private slot to handle the toggling of the autohide button. @param checked flag indicating the checked state of the button (boolean) """ self.__autoHide = not checked if self.__autoHide: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOn.png")) else: self.__autoHideButton.setIcon( UI.PixmapCache.getIcon("autoHideOff.png")) def __appFocusChanged(self, old, now): """ Private slot to handle a change of the focus. @param old reference to the widget, that lost focus (QWidget or None) @param now reference to the widget having the focus (QWidget or None) """ self.__hasFocus = self.isAncestorOf(now) if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() elif self.__autoHide and self.__hasFocus and self.isMinimized(): self.expand() def enterEvent(self, event): """ Protected method to handle the mouse entering this widget. @param event reference to the event (QEvent) """ if self.__autoHide and self.isMinimized(): self.expand() else: self.__cancelDelayTimer() def leaveEvent(self, event): """ Protected method to handle the mouse leaving this widget. @param event reference to the event (QEvent) """ if self.__autoHide and not self.__hasFocus and not self.isMinimized(): self.shrink() else: self.__cancelDelayTimer() def shutdown(self): """ Public method to shut down the object. This method does some preparations so the object can be deleted properly. It disconnects from the focusChanged signal in order to avoid trouble later on. """ e5App().focusChanged.disconnect(self.__appFocusChanged)
class AnalyzeTab(QWidget): analyzeDone = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject', 'PyQt_PyObject') def __init__(self): super().__init__() # Layouts self.mainLayout = QHBoxLayout() self.lVbox = QVBoxLayout() self.lHbox = QHBoxLayout() self.lHbox_top = QHBoxLayout() self.rVbox = QVBoxLayout() self.rHbox = QHBoxLayout() self.rVbox2 = QVBoxLayout() self.stack = QStackedWidget() self.stack_Vbox = QVBoxLayout() self.stack_Hbox1 = QHBoxLayout() self.stack_Hbox2 = QHBoxLayout() self.hSplit = QSplitter(Qt.Horizontal) self.hSplit.setFrameShape(QFrame.StyledPanel) self.vSplit = QSplitter(Qt.Vertical) self.vSplit.setFrameShape(QFrame.StyledPanel) self.mainLayout.addLayout(self.lVbox, 1) self.mainLayout.addLayout(self.rVbox, 3) # Setup file browser self.fileModel = QFileSystemModel() self.fileModel.setNameFilters(['*.wav']) self.fileModel.setRootPath(QDir.currentPath()) self.fileTree = QTreeView() self.fileTree.setModel(self.fileModel) self.fileTree.setRootIndex(self.fileModel.index(r'./')) self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection) self.fileTree.setColumnHidden(2, True) self.fileTree.setColumnHidden(1, True) self.rootDirEdit = QLineEdit(os.path.dirname(__file__)) self.rootDirEdit.returnPressed.connect(self.on_edit_root) self.browseBtn = QPushButton('Browse') self.browseBtn.clicked.connect(self.on_browse) self.lHbox_top.addWidget(self.rootDirEdit, 3) self.lHbox_top.addWidget(self.browseBtn, 1) # Setup Canvas self.canvas = PlotCanvas(self) self.analyzeDone.connect(self.canvas.plot) self._analyze = lambda _: self.analyze(self.fileTree.selectedIndexes()) self.analyzeBtn = QPushButton('Analyze') self.analyzeBtn.clicked.connect(self._analyze) ## BATCH ANALYSIS CONTROLS ## self.batchAnalyzeChk = QCheckBox('Batch Analysis') self.dataTable = QTableWidget() self.batchCtrlBox = QGroupBox("Batch Analysis") self.batchCtrlBox.setLayout(self.stack_Vbox) # Analysis Mode self.modeGroup = QButtonGroup() self.modeBox = QGroupBox('Analysis Mode') self.modeBox.setLayout(self.stack_Hbox1) self.stack_Vbox.addWidget(self.modeBox) self.wavAnalysisChk = QCheckBox('Wav analysis') self.wavAnalysisChk.setChecked(True) self.calibrationLocationBox = QComboBox() self.calibrationLocationBox.addItems([str(n) for n in range(1, 11)]) self.calibrationCurveChk = QCheckBox('Calibration Curve') self.calibrationCurveChk.toggled.connect( lambda state: self.calibrationLocationBox.setEnabled(state)) self.calibrationCurveChk.setChecked(False) self.stack_Hbox1.addWidget(self.wavAnalysisChk, 3) self.stack_Hbox1.addWidget(self.calibrationCurveChk, 3) self.stack_Hbox1.addWidget(QLabel('Location: '), 1) self.stack_Hbox1.addWidget(self.calibrationLocationBox, 1) self.stack_Vbox.addLayout(self.stack_Hbox1) self.modeGroup.addButton(self.wavAnalysisChk) self.modeGroup.addButton(self.calibrationCurveChk) self.modeGroup.setExclusive(True) # Outputs self.outputCtrlBox = QGroupBox('Outputs') self.outputCtrlBox.setLayout(self.stack_Hbox2) self.stack_Vbox.addWidget(self.outputCtrlBox) self.toCSVchk = QCheckBox('.csv') self.toJSONchk = QCheckBox('.json') self.toCSVchk.stateChanged.connect(lambda _: self.update_settings( 'output', 'toCSV', self.toCSVchk.isChecked())) self.toJSONchk.stateChanged.connect(lambda _: self.update_settings( 'output', 'toJSON', self.toJSONchk.isChecked())) self.stack_Hbox2.addWidget(self.toCSVchk) self.stack_Hbox2.addWidget(self.toJSONchk) self.stack_Vbox.addLayout(self.stack_Hbox2) self.stack.addWidget(self.dataTable) self.stack.addWidget(self.batchCtrlBox) self.stack.setCurrentWidget(self.dataTable) self.stack.show() self.batchAnalyzeChk.stateChanged.connect(self.toggle_stack) self.batchAnalyzeChk.setChecked(False) self.stack_Vbox.addStretch() ## PROCESSING CONTROLS ## self.processControls = QGroupBox('Signal Processing') self.tOffsetSlider = QSlider(Qt.Horizontal, ) self.tOffsetSlider.setMinimum(1) self.tOffsetSlider.setMaximum(100) self.tOffsetSlider.setValue(100) self.tOffsetSlider.setTickPosition(QSlider.TicksBelow) self.tOffsetSlider.setTickInterval(10) self.tOffsetSlider.valueChanged.connect( lambda val: self.update_settings('processing', 'tChop', val)) self.tOffsetLayout = QHBoxLayout() self.tOffsetSlider_Box = QGroupBox( f'Chop Signal - {self.tOffsetSlider.value()}%') self.tOffsetSlider.valueChanged.connect( lambda val: self.tOffsetSlider_Box.setTitle(f'Chop Signal - {val}%' )) self.tOffsetSlider_Box.setLayout(self.tOffsetLayout) self.tOffsetLayout.addWidget(self.tOffsetSlider) self.nFFTSlider = QSlider(Qt.Horizontal, ) self.nFFTSlider.setMinimum(1) self.nFFTSlider.setMaximum(16) self.nFFTSlider.setValue(1) self.nFFTSlider.setTickPosition(QSlider.TicksBelow) self.nFFTSlider.setTickInterval(2) self.nFFTSlider.valueChanged.connect( lambda val: self.update_settings('processing', 'detail', val)) self.nFFTLayout = QHBoxLayout() self.nFFTSlider.valueChanged.connect( lambda val: self.nFFTSlider_Box.setTitle(f'FFT Size - {val*65536}' )) self.nFFTSlider_Box = QGroupBox( f'FFT Size - {self.nFFTSlider.value()*65536}') self.nFFTSlider_Box.setLayout(self.nFFTLayout) self.nFFTLayout.addWidget(self.nFFTSlider) self.rVbox2.addWidget(self.tOffsetSlider_Box) self.rVbox2.addWidget(self.nFFTSlider_Box) self.processControls.setLayout(self.rVbox2) self.lVbox.addLayout(self.lHbox_top, 1) self.lVbox.addWidget(self.fileTree, 7) self.lVbox.addLayout(self.lHbox, 1) self.lHbox.addWidget(self.analyzeBtn, 2) self.lHbox.addWidget(self.batchAnalyzeChk, 1) self.vSplit.addWidget(self.canvas) self.vSplit.addWidget(self.hSplit) self.rVbox.addWidget(self.vSplit) self.hSplit.addWidget(self.stack) self.hSplit.addWidget(self.processControls) self.settings = { 'processing': { 'tChop': self.tOffsetSlider.value(), 'detail': self.nFFTSlider.value() }, 'output': { 'toCSV': self.toCSVchk.isChecked(), 'toJSON': self.toJSONchk.isChecked() } } self.setLayout(self.mainLayout) def on_browse(self): # Browse to file tree root directory options = QFileDialog.Options() path = QFileDialog.getExistingDirectory( self, caption="Choose root directory", options=options) self.rootDirEdit.setText(path) self.fileTree.setRootIndex(self.fileModel.index(path)) def on_edit_root(self): # Update the file tree root directory self.fileTree.setRootIndex( self.fileModel.index(self.rootDirEdit.text())) def update_settings(self, category, setting, value): # Update settings and reprocess FFT if in single analysis mode self.settings[category][setting] = value if category == 'processing' and self.fileTree.selectedIndexes(): self.analyze(self.fileTree.selectedIndexes()) def toggle_stack(self, state): if state == 2: self.stack.setCurrentWidget(self.batchCtrlBox) self.fileTree.setSelectionMode(QAbstractItemView.MultiSelection) else: self.stack.setCurrentWidget(self.dataTable) self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection) def analyze(self, filePaths): if self.batchAnalyzeChk.isChecked(): if self.wavAnalysisChk.isChecked(): self.batch_analyze_wav( [self.fileModel.filePath(path) for path in filePaths[::4]]) if self.calibrationCurveChk.isChecked(): self.generate_calibration_curve( [self.fileModel.filePath(path) for path in filePaths[::4]]) else: if os.path.isdir(self.fileModel.filePath( filePaths[0])) or len(filePaths) > 4: QMessageBox.information( self, 'Error', 'Please select only 1 file for single analysis.') return self.single_analyze_wav(self.fileModel.filePath(filePaths[0])) def single_analyze_wav(self, filePath): """ Do an FFT and find peaks on a single wav file :param filePath: file path to .wav file """ tChopped, vChopped, fVals,\ powerFFT, peakFreqs, peakAmps = Utils.AnalyzeFFT(filePath, tChop=self.settings['processing']['tChop'], detail=self.settings['processing']['detail']) self.analyzeDone.emit(tChopped, vChopped, fVals, powerFFT, peakFreqs, peakAmps, filePath) self.update_table(peakFreqs, peakAmps) def batch_analyze_wav(self, filePaths): """ Perform a batch analysis of many .wav files. Outputs FFTs and peaks in .csv or .json format :param filePaths: A list of folders containing the .wav files to be analyzed """ toCSV = self.settings['output']['toCSV'] toJSON = self.settings['output']['toJSON'] start = time.time() fileTotal = 0 for path in filePaths: if os.path.isdir(path): blockName = os.path.basename(path) print(f'Block: {blockName}') files = [ os.path.join(path, file) for file in os.listdir(path) if '.wav' in file ] fileTotal += len(files) if toCSV: if not os.path.exists(os.path.join(path, 'fft_results_csv')): os.makedirs(os.path.join(path, 'fft_results_csv')) resultFilePath = os.path.join(path, 'fft_results_csv') print('Processing FFTs...') with multiprocessing.Pool(processes=4) as pool: results = pool.starmap( Utils.AnalyzeFFT, zip(files, itertools.repeat(True), itertools.repeat(True))) results = [ result for result in results if result is not None ] peaks = [result[0] for result in results] ffts = [result[1] for result in results] print('Writing to .csv...') resultFileName = os.path.join(resultFilePath, f'{blockName}_Peaks.csv') peakFrames = pd.concat(peaks) peakFrames.to_csv(resultFileName, index=False, header=True) with concurrent.futures.ThreadPoolExecutor( max_workers=16) as executor: executor.map(self.multi_csv_write, ffts) if toJSON: if not os.path.exists( os.path.join(path, 'fft_results_json')): os.makedirs(os.path.join(path, 'fft_results_json')) print(os.path.join(path, 'fft_results_json')) print('Processing FFTs...') with multiprocessing.Pool(processes=4) as pool: results = pool.starmap( Utils.AnalyzeFFT, zip(files, itertools.repeat(True), itertools.repeat(False), itertools.repeat(True))) results = [ result for result in results if result is not None ] print('Writing to .json...') with concurrent.futures.ThreadPoolExecutor( max_workers=16) as executor: executor.map(self.multi_json_write, results) end = time.time() print( f'**Done!** {len(filePaths)} blocks with {fileTotal} files took {round(end-start, 1)}s' ) def generate_calibration_curve(self, filePaths): """ Attempt to fit an exponential function to a set of data points (x: Peak Frequency, y: Compressive strength) provided in JSON format. ex:{ "shape": "2-Hole", "testData": { "location": "1", "strength": 3.092453552, "peaks": [ { "frequency": 1134.5561082797967, "magnitude": 0.349102384777402 }] }, "waveData": [...], "freqData": [...] } Plot the curve, data points and give the function if successful. ** NOTE ** This function is still experimental and a bit buggy. Sometimes the scipy.optimize curve_fit won't converge with the initial guess given for the coeffecients. You're probably better off writing your own code. :param filePaths: A list of folders containing .jsons """ # Strike Location location = self.calibrationLocationBox.currentText() # Function to fit to the data exp_f = lambda x, a, b, c: a * np.exp(b * x) + c # Threaded method for opening all the .jsons and fitting calibCurve = ThreadedCalibrationCurve(filePaths, location, exp_f) progressDialog = QProgressDialog( f'Gettings samples for location: {location}', None, 0, len(filePaths), self) progressDialog.setModal(True) calibCurve.blocksSearched.connect(progressDialog.setValue) try: peakFreqs, strengths, popt, pcov, fitX = calibCurve.run() except Exception as e: QMessageBox.information(self, 'Error', e) return # Calculate R Squared residuals = strengths - exp_f(peakFreqs, *popt) ss_res = np.sum(residuals**2) ss_tot = np.sum((strengths - np.mean(strengths))**2) r_squared = 1 - (ss_res / ss_tot) # Plot Results fig = Figure() plt.scatter(peakFreqs, strengths) plt.plot(fitX, exp_f(fitX, *popt), '-k') ax = plt.gca() plt.text( 0.05, 0.9, f'y = {round(popt[0],3)}*exp({round(popt[1], 5)}x) + {round(popt[2], 3)}\n', ha='left', va='center', transform=ax.transAxes) plt.text(0.05, 0.85, f'R^2 = {round(r_squared,3)}', ha='left', va='center', transform=ax.transAxes) plt.title(f'Calibration Curve, Location: {location}') plt.xlabel('Frequency (Hz)') plt.ylabel('Compressive Strength (MPa)') plt.show() def multi_csv_write(self, frameTuple): frame = frameTuple[1] wavPath = frameTuple[0] resultFileDir = os.path.join(os.path.dirname(wavPath), 'fft_results_csv') resultFileName = os.path.basename(wavPath) + '_fft.csv' resultFilePath = os.path.join(resultFileDir, resultFileName) frame.to_csv(resultFilePath, index=False, header=True) def multi_json_write(self, results): data = results[0] wavPath = results[1] jsonFileDir = os.path.join(os.path.dirname(wavPath), 'fft_results_json') resultFileName = os.path.basename(wavPath) + '_fft.json' resultFilePath = os.path.join(jsonFileDir, resultFileName) # blockName = os.path.basename(os.path.dirname(wavPath)) # blockDir = os.path.join(jsonFileDir, blockName) # if not os.path.exists(blockDir): # os.makedirs(blockDir) # print(resultFilePath) with open(resultFilePath, 'w') as f: json.dump(data, f, indent=2) def update_table(self, peakFreqs, peakAmps): """ :param peakFreqs: :param peakAmps: :return: """ self.dataTable.setRowCount(2) self.dataTable.setColumnCount(len(peakFreqs) + 1) self.dataTable.setItem(0, 0, QTableWidgetItem("Frequencies: ")) self.dataTable.setItem(1, 0, QTableWidgetItem("Powers: ")) for col, freq in enumerate(peakFreqs, start=1): self.dataTable.setItem(0, col, QTableWidgetItem(str(round(freq)))) for col, power in enumerate(peakAmps, start=1): item = QTableWidgetItem(str(round(power, 3))) if power > 0.7: item.setBackground(QColor(239, 81, 28)) elif power >= 0.4: item.setBackground(QColor(232, 225, 34)) elif power < 0.4: item.setBackground(QColor(113, 232, 34)) self.dataTable.setItem(1, col, item)
if 0 <= i < 10 and 0 <= u < 10: if str(self.map.item(i, u).text()) == '.': self.map.setItem(i, u, new_cell_mul()) for v in range(10): if str(self.map.item(v, col).text()) == 'X': row = v for v in range(row - 1, row + 2): for u in range(col - 1, col + 2): if 0 <= v < 10 and 0 <= u < 10: if str(self.map.item(v, u).text()) == '.': self.map.setItem(v, u, new_cell_mul()) if __name__ == '__main__': app = QApplication(sys.argv) ready_window = ReadyMain() pvp_window = PVPMain() windows = QStackedWidget() windows.addWidget(ready_window) # 0 windows.addWidget(pvp_window) # 1 windows.setWindowTitle("Морской бой") windows.show() sys.exit(app.exec())
class DAT_GUI(QtWidgets.QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.initUI() def initUI(self): screen = QApplication.desktop().screenNumber( QApplication.desktop().cursor().pos()) centerPoint = QApplication.desktop().screenGeometry(screen).center() # should fit in 1024x768 (old computer screens) window_width = 900 window_height = 700 self.setGeometry( QtCore.QRect( centerPoint.x() - int(window_width / 2), centerPoint.y() - int(window_height / 2), window_width, window_height)) # should I rather center on the screen # zoom parameters self.scale = 1.0 self.min_scaling_factor = 0.1 self.max_scaling_factor = 20 self.zoom_increment = 0.05 self.setWindowTitle(__NAME__ + ' v' + str(__VERSION__)) self.paint = Createpaintwidget() # initiate 2D image for 2D display self.img = None self.list = QListWidget( self) # a list that contains files to read or play with self.list.setSelectionMode(QAbstractItemView.ExtendedSelection) self.list.selectionModel().selectionChanged.connect( self.selectionChanged) # connect it to sel change self.scrollArea = QScrollArea() self.scrollArea.setBackgroundRole(QPalette.Dark) self.scrollArea.setWidget(self.paint) self.paint.scrollArea = self.scrollArea self.table_widget = QWidget() table_widget_layout = QVBoxLayout() # Initialize tab screen self.tabs = QTabWidget(self) self.tab1 = QWidget() self.tab2 = QWidget() self.tab3 = QWidget() # Add tabs self.tabs.addTab(self.tab1, "Mask neuron") self.tabs.addTab(self.tab2, "Mask cell body") self.tabs.addTab(self.tab3, "Segment dendrites") # listen to tab changes self.tabs.currentChanged.connect(self._onTabChange) # Create first tab self.tab1.layout = QGridLayout() self.tab1.layout.setAlignment(Qt.AlignTop) self.tab1.layout.setHorizontalSpacing(3) self.tab1.layout.setVerticalSpacing(3) self.tab1.layout.setContentsMargins(0, 0, 0, 0) label1_tab1 = QLabel('Step 1:') self.tab1.layout.addWidget(label1_tab1, 0, 0) self.local_threshold = QPushButton("Local threshold") self.local_threshold.clicked.connect(self.run_threshold_neuron) self.tab1.layout.addWidget(self.local_threshold, 0, 1) self.global_threshold = QPushButton("Global threshold") self.global_threshold.clicked.connect(self.run_threshold_neuron) self.tab1.layout.addWidget(self.global_threshold, 0, 2) self.local_n_global_threshold = QPushButton( "Local AND Global threshold") self.local_n_global_threshold.clicked.connect( self.run_threshold_neuron) self.tab1.layout.addWidget(self.local_n_global_threshold, 0, 3) self.extra_value_for_threshold = QSpinBox() self.extra_value_for_threshold.setSingleStep(1) self.extra_value_for_threshold.setRange(0, 1_000_000) self.extra_value_for_threshold.setValue(6) self.tab1.layout.addWidget(self.extra_value_for_threshold, 0, 4) self.threshold_method = QComboBox() self.threshold_method.addItem('Mean') self.threshold_method.addItem('Median') self.tab1.layout.addWidget(self.threshold_method, 0, 5) label2_tab1 = QLabel('Step 2 (optional):') self.tab1.layout.addWidget(label2_tab1, 1, 0) self.remove_pixel_blobs_smaller_or_equal = QPushButton( "Remove pixel blobs smaller or equal to") self.remove_pixel_blobs_smaller_or_equal.clicked.connect( self.remove_blobs) self.tab1.layout.addWidget(self.remove_pixel_blobs_smaller_or_equal, 1, 1) self.remove_blobs_size = QSpinBox() self.remove_blobs_size.setSingleStep(1) self.remove_blobs_size.setRange(0, 1_000_000) self.remove_blobs_size.setValue(1) self.tab1.layout.addWidget(self.remove_blobs_size, 1, 2) label3_tab1 = QLabel('Step 3: Save') self.tab1.layout.addWidget(label3_tab1, 2, 0) self.tab1.setLayout(self.tab1.layout) self.tab2.layout = QGridLayout() self.tab2.layout.setAlignment(Qt.AlignTop) self.tab2.layout.setHorizontalSpacing(3) self.tab2.layout.setVerticalSpacing(3) self.tab2.layout.setContentsMargins(0, 0, 0, 0) label1_tab2 = QLabel('Step 1:') self.tab2.layout.addWidget(label1_tab2, 0, 0) self.detect_cell_body = QPushButton("Detect cell body") self.detect_cell_body.clicked.connect(self.detect_neuronal_cell_body) self.tab2.layout.addWidget(self.detect_cell_body, 0, 1) self.extraCutOff_cell_body = QSpinBox() self.extraCutOff_cell_body.setSingleStep(1) self.extraCutOff_cell_body.setRange(0, 1_000_000) self.extraCutOff_cell_body.setValue(5) self.tab2.layout.addWidget(self.extraCutOff_cell_body, 0, 2) erosion_label = QLabel('erosion rounds') self.tab2.layout.addWidget(erosion_label, 0, 3) self.nb_erosion_cellbody = QSpinBox() self.nb_erosion_cellbody.setSingleStep(1) self.nb_erosion_cellbody.setRange(0, 1_000_000) self.nb_erosion_cellbody.setValue(2) self.tab2.layout.addWidget(self.nb_erosion_cellbody, 0, 4) min_object_size_label = QLabel('minimum object size') self.tab2.layout.addWidget(min_object_size_label, 0, 5) self.min_obj_size_px = QSpinBox() self.min_obj_size_px.setSingleStep(1) self.min_obj_size_px.setRange(0, 1_000_000) self.min_obj_size_px.setValue(600) self.tab2.layout.addWidget(self.min_obj_size_px, 0, 6) fill_label = QLabel('fill up to') self.tab2.layout.addWidget(fill_label, 0, 7) self.fill_holes_up_to = QSpinBox() self.fill_holes_up_to.setSingleStep(1) self.fill_holes_up_to.setRange(0, 1_000_000) self.fill_holes_up_to.setValue(600) self.tab2.layout.addWidget(self.fill_holes_up_to, 0, 8) nb_dilation_cell_body_label = QLabel('nb dilation cell body') self.tab2.layout.addWidget(nb_dilation_cell_body_label, 0, 9) self.nb_dilation_cellbody = QSpinBox() self.nb_dilation_cellbody.setSingleStep(1) self.nb_dilation_cellbody.setRange(0, 1_000_000) self.nb_dilation_cellbody.setValue(2) self.tab2.layout.addWidget(self.nb_dilation_cellbody, 0, 10) label2_tab2 = QLabel('Step 2: Save') self.tab2.layout.addWidget(label2_tab2, 6, 0) self.tab2.setLayout(self.tab2.layout) self.tab3.layout = QGridLayout() self.tab3.layout.setAlignment(Qt.AlignTop) self.tab3.layout.setHorizontalSpacing(3) self.tab3.layout.setVerticalSpacing(3) self.tab3.layout.setContentsMargins(0, 0, 0, 0) label1_tab3 = QLabel('Step 1:') self.tab3.layout.addWidget(label1_tab3, 0, 0) self.wshed = QPushButton("Watershed") self.wshed.clicked.connect(self.watershed_segment_the_neuron) self.tab3.layout.addWidget(self.wshed, 0, 1) self.whsed_big_blur = QDoubleSpinBox() self.whsed_big_blur.setSingleStep(0.1) self.whsed_big_blur.setRange(0, 100) self.whsed_big_blur.setValue(2.1) self.tab3.layout.addWidget(self.whsed_big_blur, 0, 2) self.whsed_small_blur = QDoubleSpinBox() self.whsed_small_blur.setSingleStep(0.1) self.whsed_small_blur.setRange(0, 100) self.whsed_small_blur.setValue(1.4) self.tab3.layout.addWidget(self.whsed_small_blur, 0, 3) self.wshed_rm_small_cells = QSpinBox() self.wshed_rm_small_cells.setSingleStep(1) self.wshed_rm_small_cells.setRange(0, 1_000_000) self.wshed_rm_small_cells.setValue(10) self.tab3.layout.addWidget(self.wshed_rm_small_cells, 0, 4) self.jSpinner11 = QSpinBox() self.jSpinner11.setSingleStep(1) self.jSpinner11.setRange(0, 1_000_000) self.jSpinner11.setValue(10) self.tab3.layout.addWidget(self.jSpinner11, 0, 5) label1_bis_tab3 = QLabel('Alternative Step 1:') self.tab3.layout.addWidget(label1_bis_tab3, 1, 0) self.skel = QPushButton("Skeletonize") self.skel.clicked.connect(self.skeletonize_mask) self.tab3.layout.addWidget(self.skel, 1, 1) label2_tab3 = QLabel('Step 2:') self.tab3.layout.addWidget(label2_tab3, 2, 0) self.apply_cell_body = QPushButton("Apply cell body") self.apply_cell_body.clicked.connect( self.apply_cell_body_to_skeletonized_mask) self.tab3.layout.addWidget(self.apply_cell_body, 2, 1) label3_tab3 = QLabel('Step 3 (Optional):') self.tab3.layout.addWidget(label3_tab3, 3, 0) self.prune = QPushButton("Prune") self.prune.clicked.connect(self.prune_dendrites) self.tab3.layout.addWidget(self.prune, 3, 1) self.prune_length = QSpinBox() self.prune_length.setSingleStep(1) self.prune_length.setRange(0, 1_000_000) self.prune_length.setValue(3) self.tab3.layout.addWidget(self.prune_length, 3, 2) label4_tab3 = QLabel('Step 4 (Optional):') self.tab3.layout.addWidget(label4_tab3, 4, 0) self.find_neurons = QPushButton("Find neurons") self.find_neurons.clicked.connect(self.find_neurons_in_mask) self.tab3.layout.addWidget(self.find_neurons, 4, 1) self.find_neurons_min_size = QSpinBox() self.find_neurons_min_size.setSingleStep(1) self.find_neurons_min_size.setRange(0, 1_000_000) self.find_neurons_min_size.setValue(45) self.tab3.layout.addWidget(self.find_neurons_min_size, 4, 2) self.prune_unconnected_segments = QPushButton( "Prune unconnected segments (run 'Find neurons' first)") self.prune_unconnected_segments.clicked.connect( self.prune_neuron_unconnected_segments) self.tab3.layout.addWidget(self.prune_unconnected_segments, 4, 3) label6_tab3 = QLabel('Step 5: Save') self.tab3.layout.addWidget(label6_tab3, 5, 0) label5_tab3 = QLabel('Step 6:') self.tab3.layout.addWidget(label5_tab3, 6, 0) self.create_n_save_bonds = QPushButton("Segment dendrites") self.create_n_save_bonds.clicked.connect(self.save_segmented_bonds) self.tab3.layout.addWidget(self.create_n_save_bonds, 6, 1) self.tab3.setLayout(self.tab3.layout) # Add tabs to widget table_widget_layout.addWidget(self.tabs) self.table_widget.setLayout(table_widget_layout) self.Stack = QStackedWidget(self) self.Stack.addWidget(self.scrollArea) # create a grid that will contain all the GUI interface self.grid = QGridLayout() self.grid.addWidget(self.Stack, 0, 0) self.grid.addWidget(self.list, 0, 1) # The first parameter of the rowStretch method is the row number, the second is the stretch factor. So you need two calls to rowStretch, like this: --> below the first row is occupying 80% and the second 20% self.grid.setRowStretch(0, 75) self.grid.setRowStretch(2, 25) # first col 75% second col 25% of total width self.grid.setColumnStretch(0, 75) self.grid.setColumnStretch(1, 25) # void QGridLayout::addLayout(QLayout * layout, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment = 0) self.grid.addWidget(self.table_widget, 2, 0, 1, 2) # spans over one row and 2 columns # BEGIN TOOLBAR # pen spin box and connect self.penSize = QSpinBox() self.penSize.setSingleStep(1) self.penSize.setRange(1, 256) self.penSize.setValue(3) self.penSize.valueChanged.connect(self.penSizechange) self.channels = QComboBox() self.channels.addItem("merge") self.channels.currentIndexChanged.connect(self.channelChange) tb_drawing_pane = QToolBar() save_button = QToolButton() save_button.setText("Save") save_button.clicked.connect(self.save_current_mask) tb_drawing_pane.addWidget(save_button) tb_drawing_pane.addWidget(QLabel("Channels")) tb_drawing_pane.addWidget(self.channels) # tb.addAction("Save") # tb_drawing_pane.addWidget(QLabel("Pen size")) tb_drawing_pane.addWidget(self.penSize) self.grid.addWidget(tb_drawing_pane, 1, 0) # END toolbar # toolbar for the list tb_list = QToolBar() del_button = QToolButton() del_button.setText("Delete selection from list") del_button.clicked.connect(self.delete_from_list) tb_list.addWidget(del_button) self.grid.addWidget(tb_list, 1, 1) # self.setCentralWidget(self.scrollArea) self.setCentralWidget(QFrame()) self.centralWidget().setLayout(self.grid) # self.statusBar().showMessage('Ready') statusBar = self.statusBar( ) # sets an empty status bar --> then can add messages in it self.paint.statusBar = statusBar # add progress bar to status bar self.progress = QProgressBar(self) self.progress.setGeometry(200, 80, 250, 20) statusBar.addWidget(self.progress) # Set up menu bar self.mainMenu = self.menuBar() self.zoomInAct = QAction( "Zoom &In (25%)", self, # shortcut="Ctrl++", enabled=True, triggered=self.zoomIn) self.zoomOutAct = QAction( "Zoom &Out (25%)", self, # shortcut="Ctrl+-", enabled=True, triggered=self.zoomOut) self.normalSizeAct = QAction( "&Normal Size", self, # shortcut="Ctrl+S", enabled=True, triggered=self.defaultSize) self.viewMenu = QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.menuBar().addMenu(self.viewMenu) self.setMenuBar(self.mainMenu) # set drawing window fullscreen fullScreenShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_F), self) fullScreenShortcut.activated.connect(self.fullScreen) fullScreenShortcut.setContext(QtCore.Qt.ApplicationShortcut) escapeShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Escape), self) escapeShortcut.activated.connect(self.escape) escapeShortcut.setContext(QtCore.Qt.ApplicationShortcut) # Show/Hide the mask escapeShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_H), self) escapeShortcut.activated.connect(self.showHideMask) escapeShortcut.setContext(QtCore.Qt.ApplicationShortcut) zoomPlus = QtWidgets.QShortcut("Ctrl+Shift+=", self) zoomPlus.activated.connect(self.zoomIn) zoomPlus.setContext(QtCore.Qt.ApplicationShortcut) zoomPlus2 = QtWidgets.QShortcut("Ctrl++", self) zoomPlus2.activated.connect(self.zoomIn) zoomPlus2.setContext(QtCore.Qt.ApplicationShortcut) zoomMinus = QtWidgets.QShortcut("Ctrl+Shift+-", self) zoomMinus.activated.connect(self.zoomOut) zoomMinus.setContext(QtCore.Qt.ApplicationShortcut) zoomMinus2 = QtWidgets.QShortcut("Ctrl+-", self) zoomMinus2.activated.connect(self.zoomOut) zoomMinus2.setContext(QtCore.Qt.ApplicationShortcut) spaceShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Space), self) spaceShortcut.activated.connect(self.nextFrame) spaceShortcut.setContext(QtCore.Qt.ApplicationShortcut) backspaceShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Backspace), self) backspaceShortcut.activated.connect(self.prevFrame) backspaceShortcut.setContext(QtCore.Qt.ApplicationShortcut) # connect enter keys to edit dendrites enterShortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Return), self) enterShortcut.activated.connect(self.runSkel) enterShortcut.setContext(QtCore.Qt.ApplicationShortcut) enter2Shortcut = QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Enter), self) enter2Shortcut.activated.connect(self.runSkel) enter2Shortcut.setContext(QtCore.Qt.ApplicationShortcut) #Qt.Key_Enter is the Enter located on the keypad: #Qt::Key_Return 0x01000004 #Qt::Key_Enter 0x01000005 Typically located on the keypad. self.setAcceptDrops(True) # KEEP IMPORTANT def __get_mask_img_from_overlay(self): if self.paint.imageDraw: channels_count = 4 s = self.paint.imageDraw.bits().asstring( self.img.shape[0] * self.img.shape[1] * channels_count) arr = np.frombuffer(s, dtype=np.uint8).reshape( (self.img.shape[0], self.img.shape[1], channels_count)) return Img(arr[..., 2].copy(), dimensions='hw') else: return None def __get_output_folder(self): selected_items = self.list.selectedItems() if selected_items: filename = selected_items[0].toolTip() filename0_without_ext = os.path.splitext(filename)[0] return filename0_without_ext else: return None def delete_from_list(self): list_items = self.list.selectedItems() # empty list --> nothing to do if not list_items: return for item in list_items: self.list.takeItem(self.list.row(item)) def save_current_mask(self): output_folder = self.__get_output_folder() if output_folder is None: logger.error('No image is selected --> nothing to save') return mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to save') return if self.tabs.currentIndex() == 0: print('saving', os.path.join(output_folder, 'mask.tif')) mask.save(os.path.join(output_folder, 'mask.tif')) elif self.tabs.currentIndex() == 1: print('saving', os.path.join(output_folder, 'cellBodyMask.tif')) mask.save(os.path.join(output_folder, 'cellBodyMask.tif')) else: print('saving', os.path.join(output_folder, 'handCorrection.tif')) mask.save(os.path.join(output_folder, 'handCorrection.tif')) def detect_neuronal_cell_body(self): try: # get image and detect cell body mask = detect_cell_body( self.img, fillHoles=self.fill_holes_up_to.value(), denoise=self.min_obj_size_px.value(), nbOfErosions=self.nb_erosion_cellbody.value(), nbOfDilatations=self.nb_dilation_cellbody.value(), extraCutOff=self.extraCutOff_cell_body.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error('Cell body could not be detected') except: traceback.print_exc() def __get_neuronal_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'mask.tif')): # NB should I check the nb of channels --> no I don't want to handle externally created files and want people to rely fully on my stuff that has return Img(os.path.join(output_folder, 'mask.tif')) else: if warn: logger.error( 'Neuronal mask not found --> Please create one in the "Mask neuron" tab first' ) return None def __get_corrected_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'handCorrection.tif')): return Img(os.path.join(output_folder, 'handCorrection.tif')) elif os.path.exists(os.path.join(output_folder, 'mask.tif')) and not warn: return Img(os.path.join(output_folder, 'mask.tif')) return None def __get_cellbody_mask(self, warn=True): output_folder = self.__get_output_folder() if output_folder is None: if warn: logger.error('No image selected --> nothing to do') return None if os.path.exists(os.path.join(output_folder, 'cellBodyMask.tif')): # NB should I check the nb of channels --> no I don't want to handle externally created files and want people to rely fully on my stuff that has return Img(os.path.join(output_folder, 'cellBodyMask.tif')) else: if warn: logger.error( 'Cell body mask not found --> Please create one in the "Mask cell body" tab first' ) return None # seems ok for now def watershed_segment_the_neuron(self): try: # get raw image and segment it using the watershed algorithm # make it load the neuronal mask neuronal_mask = self.__get_neuronal_mask() if neuronal_mask is None: return # TODO should I add autoskel or not mask = watershed_segment_neuron( self.img, neuronal_mask, fillSize=self.jSpinner11.value(), autoSkel=True, first_blur=self.whsed_big_blur.value(), second_blur=self.whsed_small_blur.value(), min_size=self.wshed_rm_small_cells.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error( 'Something went wrong, the neuron could not be segmented, sorry...' ) except: traceback.print_exc() def save_segmented_bonds(self): output_folder = self.__get_output_folder() if output_folder is None: logger.error('No image is selected --> nothing to save') return # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask = detect_cell_bonds(mask) if mask is None: logger.error( 'Could not find dendrites, are you sure a mask is overlayed over the neuron' ) return # code for conversion of 24 bits numpy array to an RGB one --> keep and store in Img at some point cause useful # convert 24 bits array to RGB RGB_mask = np.zeros(shape=(*mask.shape, 3), dtype=np.uint8) RGB_mask[..., 2] = mask & 255 RGB_mask[..., 1] = (mask >> 8) & 255 RGB_mask[..., 0] = (mask >> 16) & 255 Img(RGB_mask, dimensions='hwc').save(os.path.join(output_folder, 'bonds.tif')) def prune_neuron_unconnected_segments(self): # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask = find_neurons( mask, neuron_minimum_size_threshold=self.find_neurons_min_size.value(), return_unconnected=True) if mask is None: logger.error( 'Could not find neurons, are you sure a mask is overlayed over the neuron' ) return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def apply_cell_body_to_skeletonized_mask(self): mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return cell_body_mask = self.__get_cellbody_mask() if cell_body_mask is None: return cell_body_outline = get_cell_body_outline(cell_body_mask, mask) if cell_body_outline is None: logger.error( 'Error could not add cell body outline to the neuronal mask...' ) return self.paint.imageDraw = Img(self.createRGBA(cell_body_outline), dimensions='hwc').getQimage() self.paint.update() def find_neurons_in_mask(self): # get mask the find neurons mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return mask_copy = mask.copy() mask = find_neurons( mask, neuron_minimum_size_threshold=self.find_neurons_min_size.value()) if mask is None: logger.error( 'Could not find neurons, are you sure a mask is overlayed over the neuron' ) return # we set the red channel, the blue one, the alpha transparency (channel 4) and finally we only allow alpha channel in the two masks regions to keep the rest of the stuff final_overlay = np.zeros(shape=(*mask_copy.shape, 4), dtype=np.uint8) final_overlay[..., 0] = np.logical_xor(mask, mask_copy).astype( np.uint8) * 255 # blue channel final_overlay[mask == 0, 0] = 0 final_overlay[..., 1] = final_overlay[ ..., 0] # green channel # copy the channel to make the stuff appear cyan final_overlay[..., 2] = mask_copy # red channel final_overlay[np.logical_or(mask, mask_copy) != 0, 3] = 255 # --> need set alpha transparency of the image self.paint.imageDraw = Img(final_overlay, dimensions='hwc').getQimage() self.paint.update() def prune_dendrites(self): prune_lgth = self.prune_length.value() if prune_lgth <= 0: logger.info('prune length is 0 --> nothing to do') return # get the mask from displayed image mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return # see how to get the stuff ???? mask = prune_dendrites(mask, prune_below=prune_lgth) if mask is None: logger.error( 'Could not prune dendrites, are you sure there is a mask ovrlayed on the neuron' ) return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def skeletonize_mask(self): # get mask then skeletonize it then return it --> see exactly try: # get raw image and segment it using the skeletonize algorithm # make it load the neuronal mask neuronal_mask = self.__get_neuronal_mask() if neuronal_mask is None: return mask = skel_segment_neuronal_mask(neuronal_mask) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() else: logger.error( 'Something went wrong, the neuron could not be sekeletonized, sorry...' ) except: traceback.print_exc() def _onTabChange(self): # if tab is changed --> do stuff # load files or warn... if self.tabs.currentIndex() == 0: mask = self.__get_neuronal_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() elif self.tabs.currentIndex() == 1: mask = self.__get_cellbody_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() elif self.tabs.currentIndex() == 2: mask = self.__get_corrected_mask(warn=False) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def run_threshold_neuron(self): try: local_or_global = 'global' if self.sender() == self.local_threshold: local_or_global = 'local' elif self.sender() == self.local_n_global_threshold: local_or_global = 'local+global' mask = threshold_neuron( self.img, mode=local_or_global, blur_method=self.threshold_method.currentText(), spin_value=self.extra_value_for_threshold.value(), channel=self.channels.currentText()) if mask is not None: self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() except: traceback.print_exc() def channelChange(self, i): if self.Stack.currentIndex() == 0: if i == 0: self.paint.setImage(self.img) else: channel_img = self.img.imCopy(c=i - 1) self.paint.setImage(channel_img) self.paint.update() def penSizechange(self): self.paint.brushSize = self.penSize.value() def selectionChanged(self): self.paint.maskVisible = True selected_items = self.list.selectedItems() if selected_items: start = timer() if self.img is not None: # make sure we don't load the image twice if selected_items[0].toolTip() != self.img.metadata['path']: self.img = Img(selected_items[0].toolTip()) logger.debug("took " + str(timer() - start) + " secs to load image") else: logger.debug("image already loaded --> ignoring") else: self.img = Img(selected_items[0].toolTip()) logger.debug("took " + str(timer() - start) + " secs to load image") if self.img is not None: selection = self.channels.currentIndex() self.channels.disconnect() self.channels.clear() comboData = ['merge'] if self.img.has_c(): for i in range(self.img.get_dimension('c')): comboData.append(str(i)) logger.debug('channels found ' + str(comboData)) self.channels.addItems(comboData) if selection != -1 and selection < self.channels.count(): self.channels.setCurrentIndex(selection) else: self.channels.setCurrentIndex(0) self.channels.currentIndexChanged.connect(self.channelChange) if selected_items: self.statusBar().showMessage('Loading ' + selected_items[0].toolTip()) selection = self.channels.currentIndex() if selection == 0: self.paint.setImage(self.img) else: self.paint.setImage(self.img.imCopy(c=selection - 1)) self.scaleImage(0) self.update() self.paint.update() if self.list.currentItem() and self.list.currentItem().icon( ).isNull(): logger.debug('Updating icon') icon = QIcon(QPixmap.fromImage(self.paint.image)) pixmap = icon.pixmap(24, 24) icon = QIcon(pixmap) self.list.currentItem().setIcon(icon) else: logger.debug("Empty selection") self.paint.image = None self.scaleImage(0) self.update() self.paint.update() self.img = None # try update also the masks if they are available try: self._onTabChange() except: pass def clearlayout(self, layout): for i in reversed(range(layout.count())): layout.itemAt(i).widget().setParent(None) def showHideMask(self): self.paint.maskVisible = not self.paint.maskVisible self.paint.update() def escape(self): if self.Stack.isFullScreen(): self.fullScreen() def fullScreen(self): if not self.Stack.isFullScreen(): self.Stack.setWindowFlags( QtCore.Qt.Window | QtCore.Qt.CustomizeWindowHint | # QtCore.Qt.WindowTitleHint | # QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowStaysOnTopHint) self.Stack.showFullScreen() else: self.Stack.setWindowFlags(QtCore.Qt.Widget) self.grid.addWidget(self.Stack, 0, 0) # dirty hack to make it repaint properly --> obviously not all lines below are required but some are --> need test, the last line is key though self.grid.update() self.Stack.update() self.Stack.show() self.centralWidget().setLayout(self.grid) self.centralWidget().update() self.update() self.show() self.repaint() self.Stack.update() self.Stack.repaint() self.centralWidget().repaint() def nextFrame(self): idx = self.list.model().index(self.list.currentRow() + 1, 0) if idx.isValid(): self.list.selectionModel().setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect) # SelectCurrent def remove_blobs(self): blob_size = self.remove_blobs_size.value() if blob_size <= 0: logger.info('blob size is 0 --> nothing to do') return # get the mask from displayed image mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to save') return mask = remove_small_objects(mask.astype(np.bool), min_size=blob_size, connectivity=2, in_place=False) # then place back pixels in the mask # now set the mask back # plt.imshow(mask) # plt.show() self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def runSkel(self): # only allow that for tab 3 if self.tabs.currentIndex() == 2: mask = self.__get_mask_img_from_overlay() if mask is None: logger.error('No mask/overlay detected --> nothing to do') return # just skeletonize the image mask = skel_segment_neuronal_mask( mask, fill_holes=0) # should I put it to 0 or other things ??? if mask is None: logger.error('Could not skeletonize user edited mask...') return self.paint.imageDraw = Img(self.createRGBA(mask), dimensions='hwc').getQimage() self.paint.update() def createRGBA(self, handCorrection): # use pen color to display the mask # in fact I need to put the real color RGBA = np.zeros((handCorrection.shape[0], handCorrection.shape[1], 4), dtype=np.uint8) red = self.paint.drawColor.red() green = self.paint.drawColor.green() blue = self.paint.drawColor.blue() # bug somewhere --> fix it some day --> due to bgra instead of RGBA RGBA[handCorrection != 0, 0] = blue # b RGBA[handCorrection != 0, 1] = green # g RGBA[handCorrection != 0, 2] = red # r RGBA[..., 3] = 255 # alpha --> indeed alpha RGBA[handCorrection == 0, 3] = 0 # very complex fix some day return RGBA def prevFrame(self): idx = self.list.model().index(self.list.currentRow() - 1, 0) if idx.isValid(): self.list.selectionModel().setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect) def zoomIn(self): self.statusBar().showMessage('Zooming in', msecs=200) if self.Stack.currentIndex() == 0: self.scaleImage(self.zoom_increment) def zoomOut(self): self.statusBar().showMessage('Zooming out', msecs=200) if self.Stack.currentIndex() == 0: self.scaleImage(-self.zoom_increment) def defaultSize(self): self.paint.adjustSize() self.scale = 1.0 self.scaleImage(0) def scaleImage(self, factor): self.scale += factor if self.paint.image is not None: self.paint.resize(self.scale * self.paint.image.size()) else: # no image set size to 0, 0 --> scroll pane will auto adjust self.paint.resize(QSize(0, 0)) self.scale -= factor # reset zoom self.paint.scale = self.scale # self.paint.vdp.scale = self.scale self.zoomInAct.setEnabled(self.scale < self.max_scaling_factor) self.zoomOutAct.setEnabled(self.scale > self.min_scaling_factor) # allow DND def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() else: event.ignore() # handle DND on drop def dropEvent(self, event): if event.mimeData().hasUrls: event.setDropAction(QtCore.Qt.CopyAction) event.accept() urls = [] for url in event.mimeData().urls(): urls.append(url.toLocalFile()) for url in urls: import os item = QListWidgetItem(os.path.basename(url), self.list) item.setToolTip(url) self.list.addItem(item) else: event.ignore()