class BaseDialog(QDialog): def __init__(self, parent, title, ok_text, cancel_text, ok_button_column=0, cancel_button_column=1, buttons_to_new_row=True): super().__init__(parent) self.setWindowTitle(title) self.layout = QGridLayout(self) self.setLayout(self.layout) self.create_ui() buttons_row = self.layout.rowCount( ) if not buttons_to_new_row else self.layout.rowCount() + 1 ok_button = QPushButton(ok_text, self) ok_button.setDefault(True) ok_button.clicked.connect(self.ok_clicked) self.layout.addWidget(ok_button, buttons_row, ok_button_column) cancel_button = QPushButton(cancel_text, self) cancel_button.clicked.connect(self.reject) self.layout.addWidget(cancel_button, buttons_row, cancel_button_column) def ok_clicked(self): self.accept()
class PasswordDialog(QDialog): """ An add password Dialog """ def __init__(self, optional_fields=[]): QDialog.__init__(self) self.setMinimumHeight(120) self.setMinimumWidth(380) self.setWindowTitle('Enter a Password') self.grid_layout = QGridLayout() self.setLayout(self.grid_layout) name_label = QLabel('Name:') pass_label = QLabel('Password:'******'Gen') generate_password_button.clicked.connect(self.generate_password) self.layout().addWidget(generate_password_button, 1, 2) self.grid_layout.addWidget(pass_label, 1, 0) self.grid_layout.addWidget(self.password_input, 1, 1) self.optional_fields = list() for field in optional_fields: self.__add_optional_field__(field) comment_label = QLabel('comments') self.comment_field = QTextEdit() self.layout().addWidget(comment_label, self.layout().rowCount() + 1, 0) self.layout().addWidget(self.comment_field, self.layout().rowCount(), 1) self.confirm_button = QPushButton() self.confirm_button.setShortcut('Return') self.confirm_button.setText('OK') self.grid_layout.addWidget(self.confirm_button, self.grid_layout.rowCount() + 1, 1) self.confirm_button.clicked.connect(self.confirm) def generate_password(self): self.password_input.setText(random_password()) def confirm(self): """ confirms the add password dialog :return: None """ self.accept() def __add_optional_field__(self, name): """ adds an optional field to the Dialog :param name: :return: """ next_row = self.grid_layout.rowCount() + 1 label = QLabel('{name}:'.format(name=name)) input_field = QLineEdit() self.grid_layout.addWidget(label, next_row, 0) self.grid_layout.addWidget(input_field, next_row, 1) self.optional_fields.append((label, input_field))
def __init__(self, weapon: Weapon, parent: QWidget): # noinspection PyTypeChecker super(WeaponView, self).__init__(parent, Qt.WindowType.Dialog) layout = QGridLayout() layout.addWidget( QLabel(weapon.name.title()), 0, 0, 1, 3, ) layout.addWidget( QLabel(weapon.type.value.title()), 0, 3, ) if weapon.character is not None: character = f'{weapon.character.name.title()} only' else: character = 'All characters' layout.addWidget( QLabel(character), 1, 0, 1, 3, ) # if weapon.game is not None: # layout.addWidget( # QLabel(f'{weapon.game}'), # 1, 3, # ) stats_row = layout.rowCount() for i, s in enumerate(weapon.stats): layout.addWidget( QLabel(f'{Stats.titles[i]}: {weapon.stats[i]}'), stats_row, i, ) self.setWindowTitle(weapon.name.title()) self.setWindowModality(Qt.WindowModality.WindowModal) self.setLayout(layout)
class _ExecuteTab(QTabWidget): """Tab used to execute modules or shell commands on the selected bot.""" def __init__(self, responses_tab, model): """ :type responses_tab: _ResponsesTab """ super(_ExecuteTab, self).__init__() self._model = model self._current_layout = None self._current_bot = None self._layout = QGridLayout() self._sub_layout = QVBoxLayout() self._module_view = ModuleView(responses_tab) self._layout.setAlignment(Qt.AlignTop) self.setLayout(self._layout) self.set_empty_layout() def set_current_bot(self, bot): """Sets the connected bot this tab will interact with. :type bot: Bot """ self._current_bot = bot def _clear_layout(self): while self._layout.count(): child = self._layout.takeAt(0) if child.widget(): child.widget().deleteLater() while self._sub_layout.count(): child = self._sub_layout.takeAt(0) if child.widget(): child.widget().deleteLater() def set_empty_layout(self): """Default layout shown when the user has not yet selected a row.""" self._current_layout = "Empty" self._clear_layout() self._layout.addWidget(QLabel("Please select a bot in the table above."), 0, 0) def set_module_layout(self, module_name="screenshot"): """Sets the layout which can execute modules. :type module_name: str """ self._current_layout = "Module" self._clear_layout() command_type_label = QLabel("Command type: ") command_type_combobox = QComboBox() command_type_combobox.addItem("Module") command_type_combobox.addItem("Shell") module_label = QLabel("Module name: ") module_combobox = QComboBox() for module_name in modules.get_names(): module_combobox.addItem(module_name) module_combobox.currentTextChanged.connect(self._on_module_change) command_type_combobox.currentTextChanged.connect(self._on_command_type_change) self._layout.setColumnStretch(1, 1) self._layout.addWidget(command_type_label, 0, 0) self._layout.addWidget(command_type_combobox, 0, 1) self._layout.addWidget(module_label, 1, 0) self._layout.addWidget(module_combobox, 1, 1) # Module layout cached_module = modules.get_module(module_name) if not cached_module: cached_module = modules.load_module(module_name, self._module_view, self._model) input_fields = [] for option_name in cached_module.get_setup_messages(): input_field = QLineEdit() self._sub_layout.addWidget(QLabel(option_name)) self._sub_layout.addWidget(input_field) input_fields.append(input_field) run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) run_button.pressed.connect(lambda: self._on_module_run(module_combobox.currentText(), input_fields)) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2) self._on_module_change(module_combobox.currentText()) def set_shell_layout(self): """Sets the layout which can execute shell commands.""" self._current_layout = "Shell" self._clear_layout() command_type_label = QLabel("Command type: ") command_type_combobox = QComboBox() command_type_combobox.addItem("Shell") command_type_combobox.addItem("Module") command_label = QLabel("Command:") command_input = QLineEdit() run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) command_type_combobox.currentTextChanged.connect(self._on_command_type_change) run_button.pressed.connect(lambda: self._on_command_run(command_input)) self._layout.addWidget(command_type_label, 0, 0) self._layout.addWidget(command_type_combobox, 0, 1) self._layout.addWidget(command_label, 1, 0) self._layout.addWidget(command_input, 1, 1) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2) def _on_command_type_change(self, text): """Handles the command type combobox change event. :type text: str """ if text == "Module": self.set_module_layout() else: self.set_shell_layout() def _on_module_change(self, module_name): """Handles module combobox changes. :type module_name: str """ while self._sub_layout.count(): child = self._sub_layout.takeAt(0) if child.widget(): child.widget().deleteLater() cached_module = modules.get_module(module_name) if not cached_module: cached_module = modules.load_module(module_name, self._module_view, self._model) input_fields = [] for option_name in cached_module.get_setup_messages(): input_field = QLineEdit() input_fields.append(input_field) self._sub_layout.addWidget(QLabel(option_name)) self._sub_layout.addWidget(input_field) run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) run_button.pressed.connect(lambda: self._on_module_run(module_name, input_fields)) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) def display_info(self, text): """ :type text: str """ message_box = QMessageBox() message_box.setIcon(QMessageBox.Information) message_box.setWindowTitle("Information") message_box.setText(text) message_box.setStandardButtons(QMessageBox.Ok) message_box.exec_() def _on_module_run(self, module_name, input_fields): """Handles running modules. :type module_name: str :type input_fields: list """ set_options = [] for input_field in input_fields: set_options.append(input_field.text()) module = modules.get_module(module_name) if not module: module = modules.load_module(module_name, self._module_view, self._model) successful, options = module.setup(set_options) if successful: if module_name == "remove_bot": code = loaders.get_remove_code(self._current_bot.loader_name) elif module_name == "update_bot": code = loaders.get_update_code(self._current_bot.loader_name) else: code = modules.get_code(module_name) if not options: options = {} options["module_name"] = module_name self._model.add_command(self._current_bot.uid, Command( CommandType.MODULE, code, options )) self.display_info("Module added to the queue of:\n {}@{}".format( self._current_bot.username, self._current_bot.hostname) ) def _on_command_run(self, command_input): """Handles running commands. :type command_input: QLineEdit """ if command_input.text().strip() == "": return self._model.add_command(self._current_bot.uid, Command(CommandType.SHELL, command_input.text().encode())) command_input.clear() self.display_info("Command added to the queue of:\n {}@{}".format( self._current_bot.username, self._current_bot.hostname ))
class BasicFormCollapsible(CollapsibleBox): def __init__(self, parent, pynwb_class, metadata=None): """Basic Groupbox filling form.""" super().__init__(title=pynwb_class.__name__, parent=parent) self.parent = parent self.group_type = pynwb_class.__name__ self.metadata = metadata self.pynwb_class = pynwb_class self.groups_list = [] from nwb_conversion_tools.gui.utils.name_references import name_to_gui_class self.name_to_gui_class = name_to_gui_class # Forms-creation basic info instructions self.fill_fields_info() # Updates specific fields from specific classes that inherit BasicFormCollapsible self.fields_info_update() # Constructs GUI forms using self.fields_info dictionary self.make_forms() def fill_fields_info(self): """Fills the fields info details dictionary.""" # Loops through list of fields from class and store info in dictionary self.fields = self.pynwb_class.__init__.__docval__['args'] self.fields_info = [] for field in self.fields: # Required fields get a red star in their label if 'default' not in field: required = True # Optional fields else: required = False # Skip data types, continue looping if 'shape' in field: continue # Skip Iterable type, continue looping if field['type'] == Iterable: continue # String types if field['type'] is str: self.fields_info.append({ 'name': field['name'], 'type': 'str', 'class': None, 'required': required, 'doc': field['doc'] }) # Float types elif field['type'] in ('float', float): self.fields_info.append({ 'name': field['name'], 'type': 'float', 'class': None, 'required': required, 'doc': field['doc'] }) def fields_info_update(self): """Updates fields info with specific fields from the inheriting class.""" pass def make_forms(self): """ Initializes forms.""" # Forms grid, where each row: [label: form] self.grid = QGridLayout() self.grid.setColumnStretch(5, 1) validator_float = QDoubleValidator() # Loops through fields info to create a form entry for each for ii, field in enumerate(self.fields_info): # Required fields get a red star in their label if field['required']: field_label = field[ 'name'] + "<span style='color:" + required_asterisk_color + ";'>*</span>:" else: field_label = field['name'] + ":" # String types if field['type'] == 'str': form = QLineEdit('') # Float types elif field['type'] == 'float': form = QLineEdit('') form.setValidator(validator_float) # Link types elif field['type'] == 'link': form = CustomComboBox() # Group types elif field['type'] == 'group': setattr(self, field['name'] + '_layout', QVBoxLayout()) form = QGroupBox() form.setLayout(getattr(self, field['name'] + '_layout')) lbl = QLabel(field_label) setattr(self, 'lbl_' + field['name'], lbl) setattr(self, 'form_' + field['name'], form) getattr(self, 'form_' + field['name']).setToolTip(field['doc']) self.grid.addWidget(getattr(self, 'lbl_' + field['name']), ii, 0, 1, 2) self.grid.addWidget(getattr(self, 'form_' + field['name']), ii, 2, 1, 4) def refresh_objects_references(self, metadata=None): """ Refreshes references with existing objects in parent / grandparent groups. Refreshes children's references. """ # Refreshes self comboboxes for field in self.fields_info: if field['type'] == 'link': form = getattr(self, 'form_' + field['name']) form.clear() form_gui_class = self.name_to_gui_class[field['class']] # Search through parent for grp in self.parent.groups_list: # Adds existing specfic groups to combobox if isinstance(grp, form_gui_class): getattr(self, 'form_' + field['name']).addItem( grp.form_name.text()) # Search through grandparent if hasattr(self.parent.parent, 'groups_list'): for grp in self.parent.parent.groups_list: # Adds existing specfic groups to combobox if isinstance(grp, form_gui_class): getattr(self, 'form_' + field['name']).addItem( grp.form_name.text()) # Refreshes children for child in self.groups_list: child.refresh_objects_references(metadata=metadata) def read_fields(self): """Reads fields and returns them structured in a dictionary.""" metadata = {} n_fields = self.grid.rowCount() for i in range(n_fields): # Get field name name = self.grid.itemAtPosition(i, 0).widget().text() if '<' in name: name = name.split('<')[0] if ':' in name: name = name.replace(':', '') # Get field values group = self.grid.itemAtPosition(i, 2).widget() if isinstance(group, QLineEdit): try: metadata[name] = float(group.text()) except: metadata[name] = group.text() if isinstance(group, QComboBox): metadata[name] = str(group.currentText()) if isinstance(group, QGroupBox): metadata[name] = [] for ii in range(group.children()[0].count()): item = group.children()[0].itemAt(ii).widget() metadata[name].append(item.read_fields()) return metadata def write_fields(self, metadata={}): """Reads structured dictionary and write in form fields.""" # Loops through fields info list for i, field in enumerate(self.fields_info): # Write metadata to field if field['name'] in metadata: group = self.grid.itemAtPosition(i, 2).widget() # If field form is a string or float if isinstance(group, QLineEdit): group.setText(str(metadata[field['name']])) # If field form is a link if isinstance(group, QComboBox): group.clear() group.addItem(str(metadata[field['name']])) # If field form is a group if isinstance(group, QGroupBox): n_items = group.children()[0].count() for ind, sps in enumerate(metadata[field['name']]): if ind >= n_items: item_class = self.name_to_gui_class[field['class']] item = item_class(self, metadata={}) item.write_fields( metadata=metadata[field['name']][ind]) self.groups_list.append(item) getattr(self, field['name'] + '_layout').addWidget(item) self.setContentLayout(self.grid)
class QBaseDefenseGroupInfo(QGroupBox): def __init__(self, cp: ControlPoint, ground_object: TheaterGroundObject, game): super(QBaseDefenseGroupInfo, self).__init__("Group : " + ground_object.obj_name) self.ground_object = ground_object self.cp = cp self.game = game self.buildings = game.theater.find_ground_objects_by_obj_name( self.ground_object.obj_name) self.main_layout = QVBoxLayout() self.unit_layout = QGridLayout() self.init_ui() def init_ui(self): self.buildLayout() self.main_layout.addLayout(self.unit_layout) if not self.cp.captured and not self.ground_object.is_dead: attack_button = QPushButton("Attack") attack_button.setProperty("style", "btn-danger") attack_button.setMaximumWidth(180) attack_button.clicked.connect(self.onAttack) self.main_layout.addWidget(attack_button, 0, Qt.AlignLeft) if self.cp.captured: manage_button = QPushButton("Manage") manage_button.setProperty("style", "btn-success") manage_button.setMaximumWidth(180) manage_button.clicked.connect(self.onManage) self.main_layout.addWidget(manage_button, 0, Qt.AlignLeft) self.setLayout(self.main_layout) def buildLayout(self): unit_dict = {} for i in range(self.unit_layout.rowCount()): for j in range(self.unit_layout.columnCount()): item = self.unit_layout.itemAtPosition(i, j) if item is not None and item.widget() is not None: item.widget().setParent(None) print("Remove " + str(i) + ", " + str(j)) for g in self.ground_object.groups: for u in g.units: if u.type in unit_dict.keys(): unit_dict[u.type] = unit_dict[u.type] + 1 else: unit_dict[u.type] = 1 i = 0 for k, v in unit_dict.items(): icon = QLabel() if k in VEHICLES_ICONS.keys(): icon.setPixmap(VEHICLES_ICONS[k]) else: icon.setText("<b>" + k[:8] + "</b>") icon.setProperty("style", "icon-armor") self.unit_layout.addWidget(icon, i, 0) unit_display_name = k unit_type = vehicles.vehicle_map.get(k) if unit_type is not None: unit_display_name = db.unit_get_expanded_info( self.game.enemy_country, unit_type, 'name') self.unit_layout.addWidget( QLabel( str(v) + " x " + "<strong>" + unit_display_name + "</strong>"), i, 1) i = i + 1 if len(unit_dict.items()) == 0: self.unit_layout.addWidget(QLabel("/"), 0, 0) self.setLayout(self.main_layout) def onAttack(self): Dialog.open_new_package_dialog(self.ground_object, parent=self.window()) def onManage(self): self.edition_menu = QGroundObjectMenu(self.window(), self.ground_object, self.buildings, self.cp, self.game) self.edition_menu.show() self.edition_menu.changed.connect(self.onEdition) def onEdition(self): self.buildLayout()
def add_update_row(description: str, count: int, layout: QGridLayout) -> None: row = layout.rowCount() layout.addWidget(QLabel(f"<b>{description}</b>"), row, 0) layout.addWidget(QLabel(f"{count}"), row, 1)
class ChannelArithmeticDialog(ieb.ImarisExtensionBase): """ Channel Arithmetic and Beyond ============================= `View on GitHub <https://github.com/niaid/imaris_extensions>`_ This program enables one to specify arithmetic expressions which are used to create new channels. The basic arithmetic operations are supported: +,-,*,/,**. More advanced operations that run short `SimpleITK <https://simpleitk.org/>`_ code snippets are also supported. Channels are referenced using square brackets and the channel index, starting at **zero**. To apply an expression to all channels, use the channel index 'i'. When creating a single new channel, the arithmetic expression consists of literal channel numbers, one can select a name and color for the new channel. When creating multiple new channels, the arithmetic expression is applied to all channels, the postfix '_modified' is appended to the original channel names and the original color is copied over. Note that for all channels created by the program the channel description will include the arithmetic expression used to create that channel. This transparently supports your efforts to conduct reproducible research. Because an Imaris image has a specific pixel type (8, 16, 32 bit unsigned integer and 32 bit floating point) all computations are performed using a 32 bit floating point representation and then clamped to the range of the image's pixel type. The program allows you to use the same expression on multiple files. In this case literal channel values are limited by the number of shared channels. Thus, if working with two files one with three channels and one with four channels, the valid literal channel values are limited to 0, 1, 2. We cannot use 3 as it does not exist in all files. On the other hand, if our autofluorescence channel is one of these channels, e.g. channel 0, we can subtract it from all channels in both files, `[i]-[0]`. Basic Examples -------------- Multiply channels zero and three: .. code-block:: Python [0]*[3] Multiply channels zero and three and subtract the result from channel four: .. code-block:: Python [4] - ([0]*[3]) Duplicate all channels: .. code-block:: Python [i] Subtract channel zero from all channels: .. code-block:: Python [i]-[0] Advanced Examples ----------------- Threshold channel one using a value of 100, resulting image is binary values in {0,1}: .. code-block:: Python [1]>100 Threshold a specific channel to create a binary result using the Otsu filter: .. code-block:: Python sitk.OtsuThreshold([1], 0, 1) Threshold a specific channel retaining the values above the threshold: .. code-block:: Python sitk.Cast([1]>100, sitk.sitkFloat32)*[1] Threshold a specific channel, get all connected components, then sort the components according to size, discarding those smaller than a minimum size and create a binary mask corresponding to the largest component, which is the first label(second largest component label is 2 etc.) .. code-block:: Python sitk.RelabelComponent(sitk.ConnectedComponent([1]>100), minimumObjectSize = 50)==1 Create a binary mask representing the colocalization of two channels, intensity values below 20 are considred noise: .. code-block:: Python ([1]>20)*([2]>20) Create a binary mask representing the colocalization of two channels. We are interested in all pixels in channel 2 that have a value above 20 and that are less than 1.0um away from pixels in channel 1 that have a value above 100 (**note**: this operation yields different results when run using a slice-by-slice approach vs. a volumetric approach): .. code-block:: Python (sitk.Cast([2]>20, sitk.sitkFloat32) * sitk.Abs(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)))<=1.0 Create a binary mask using thresholding and then perform morphological closing (dilation followed by erosion) with distance maps, useful for filling holes: .. code-block:: Python sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < 1.0, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<-1.0 Create a binary mask using thresholding and then perform morphological opening (erosion followed by dilation) with distance maps, useful for removing small islands: .. code-block:: Python sitk.SignedMaurerDistanceMap(sitk.SignedMaurerDistanceMap([1]>100, insideIsPositive=False, squaredDistance=False, useImageSpacing=True) < -0.2, insideIsPositive=False, squaredDistance=False, useImageSpacing=True)<0.2 """ # noqa def __init__(self): super(ChannelArithmeticDialog, self).__init__() # Channel indexes in the arithmetic calculator are denoted using a # regular expression: one or more digits in square brackets (e.g. [1234]). # First digit is zero and nothing afterwards or first digit is in [1-9] and # there are possibly more digits afterwards. # Starting index is zero. self.channel_pattern = re.compile(r"\[(0|[1-9]\d*)\]") # Use QT's global threadpool, documentation says: "This global thread pool # automatically maintains an optimal number of threads based on the # number of cores in the CPU." self.threadpool = QThreadPool.globalInstance() # Configure the help dialog. self.help_dialog = HelpDialog(w=700, h=500) self.help_dialog.setWindowTitle("Channel Arithmetic Help") self.help_dialog.set_rst_text( inspect.getdoc(self), pygments_css_file_name="pygments_dark.css") self.__create_gui() self.setWindowTitle("Channel Arithmetic") self.processing_error = False self.show() def __create_gui(self): menu_bar = self.menuBar() # Force menubar to be displayed in the application on OSX/Linux, otherwise it # is displayed in the system menubar menu_bar.setNativeMenuBar(False) self.help_button = QPushButton("Help") self.help_button.clicked.connect(self.help_dialog.show) menu_bar.setCornerWidget(self.help_button, Qt.TopLeftCorner) central_widget = QWidget(self) gui_layout = QVBoxLayout() central_widget.setLayout(gui_layout) self.setCentralWidget(central_widget) select_files_widget = self.__create_select_files_widget() arithmetic_widget = self.__create_arithmetic_widget() self.stack = QStackedWidget(self) self.stack.addWidget(select_files_widget) self.stack.addWidget(arithmetic_widget) gui_layout.addWidget(self.stack) self.status_bar = self.statusBar() def closeEvent(self, event): """ Override the closeEvent method so that clicking the 'x' button also closes all of the dialogs. """ self.help_dialog.close() event.accept() def __create_arithmetic_widget(self): wid = QWidget(self) arithmetic_layout = QVBoxLayout() wid.setLayout(arithmetic_layout) self.valid_indexes_label = QLabel("") arithmetic_layout.addWidget(self.valid_indexes_label) layout = QHBoxLayout() layout.setAlignment(Qt.AlignLeft) layout.addWidget(QLabel("Enter new channel arithmetic expression:")) arithmetic_layout.addLayout(layout) self.arithmetic_expression_text_edit = QTextEdit() arithmetic_layout.addWidget(self.arithmetic_expression_text_edit) self.slice_by_slice_checkbox = QCheckBox( "Slice by slice (smaller memory footprint).") arithmetic_layout.addWidget(self.slice_by_slice_checkbox) layout = QHBoxLayout() layout.addWidget(QLabel("New channel name:")) self.new_channel_name_line_edit = QLineEdit() layout.addWidget(self.new_channel_name_line_edit) arithmetic_layout.addLayout(layout) layout = QHBoxLayout() layout.addWidget(QLabel("New channel color:")) self.new_channel_color_button = QPushButton() self.new_channel_color_button.clicked.connect( self.__select_color_callback) layout.addWidget(self.new_channel_color_button) arithmetic_layout.addLayout(layout) self.apply_button = QPushButton("Apply") self.apply_button.clicked.connect(self.__channel_arithmetic_wrapper) arithmetic_layout.addWidget(self.apply_button) progress_wid = QWidget() self.progress_grid_layout = QGridLayout() progress_wid.setLayout(self.progress_grid_layout) scroll_area = QScrollArea() scroll_area.setWidget(progress_wid) scroll_area.setWidgetResizable(True) arithmetic_layout.addWidget(scroll_area) layout = QHBoxLayout() layout.setAlignment(Qt.AlignLeft) self.processing_prev_button = QPushButton("Prev") self.processing_prev_button.clicked.connect( lambda: self.stack.setCurrentIndex(0)) layout.addWidget(self.processing_prev_button) arithmetic_layout.addLayout(layout) return wid def __configure_and_show_arithmetic_widget(self): file_names = self.input_files_edit.toPlainText().split("\n") num_channels = [] problematic_images = [] for file_name in file_names: try: meta_data = sio.read_metadata(file_name) num_channels.append(len(meta_data["channels_information"])) except Exception: problematic_images.append(file_name) if problematic_images: self._error_function( "Problem encountered reading the following file(s):\n" + "\n".join(problematic_images)) return self.max_channel_index = min(num_channels) - 1 self.valid_indexes_label.setText( f"Valid channel indexes: 0...{self.max_channel_index}, i") self.arithmetic_expression_text_edit.clear() self.slice_by_slice_checkbox.setChecked(False) self.new_channel_name_line_edit.clear() # Remove all widgets from layout, done in reverse order because # removing from the begining shifts the rest of the items for i in reversed(range(self.progress_grid_layout.count())): self.progress_grid_layout.itemAt(i).widget().setParent(None) for i, file_name in enumerate(file_names): self.progress_grid_layout.addWidget( QLabel(os.path.basename(file_name)), i, 0) progress_bar = QProgressBar() progress_bar.setMaximum(100) self.progress_grid_layout.addWidget(progress_bar, i, 1) self.stack.setCurrentIndex(1) def __create_select_files_widget(self): wid = QWidget() input_layout = QVBoxLayout() wid.setLayout(input_layout) layout = QHBoxLayout() layout.addWidget(QLabel("File names:")) layout.setAlignment(Qt.AlignLeft) button = QPushButton("Browse") button.setToolTip("Select input files for arithmetic operation.") button.clicked.connect(self.__browse_select_input_callback) layout.addWidget(button) input_layout.addLayout(layout) self.input_files_edit = QTextEdit() self.input_files_edit.setReadOnly(True) input_layout.addWidget(self.input_files_edit) layout = QHBoxLayout() layout.setAlignment(Qt.AlignRight) self.input_files_next_button = QPushButton("Next") self.input_files_next_button.setEnabled(False) self.input_files_next_button.clicked.connect( self.__configure_and_show_arithmetic_widget) layout.addWidget(self.input_files_next_button) input_layout.addLayout(layout) return wid def __browse_select_input_callback(self): file_names, _ = QFileDialog.getOpenFileNames( self, "QFileDialog.getOpenFileNames()", "", "Imaris Images (*.ims);;All Files (*)", ) if file_names: self.input_files_edit.setText("\n".join(file_names)) self.input_files_next_button.setEnabled(True) def __select_color_callback(self): color = QColorDialog.getColor() if color.isValid(): self.new_channel_color_button.setStyleSheet( f"background-color :rgb({color.red()},{color.green()},{color.blue()})" ) def __channel_arithmetic_wrapper(self): # Get the arithmetic expression after removing all whitespace arithmetic_expression = "".join( self.arithmetic_expression_text_edit.toPlainText().split()) color = self.new_channel_color_button.palette().button().color() if arithmetic_expression: # Get the explicit channel indexes that appear in the expression and # check that they are in the valid range. channel_indexes = re.findall(self.channel_pattern, arithmetic_expression) invalid_channels = [ ci for ci in channel_indexes if int(ci) not in range(self.max_channel_index + 1) ] if invalid_channels: self._error_function( "The following channels specified in the arithmetic expression" + f" are outside the valid range [0,{self.max_channel_index}]: " + ", ".join(invalid_channels)) return # Disable the UI interaction during computation self.arithmetic_expression_text_edit.setReadOnly(True) self.slice_by_slice_checkbox.setEnabled(False) self.new_channel_name_line_edit.setReadOnly(True) self.new_channel_color_button.setEnabled(False) self.apply_button.setEnabled(False) self.processing_prev_button.setEnabled(False) QApplication.setOverrideCursor(Qt.WaitCursor) file_names = self.input_files_edit.toPlainText().split("\n") self.num_threads_left = len(file_names) for i, input_file_name in enumerate(file_names): # Configure and perform computation in another thread. arithmetic_calculator = ArithmeticCalculator( self.channel_pattern) arithmetic_calculator.signals.finished.connect( self.__arithmetic_finished) arithmetic_calculator.signals.processing_error.connect( self._processing_error_function) arithmetic_calculator.signals.progress_signal.connect( self.progress_grid_layout.itemAtPosition( i, 1).widget().setValue) arithmetic_calculator.signals.update_state_signal.connect( self.status_bar.showMessage) arithmetic_calculator.input_file_name = input_file_name arithmetic_calculator.arithmetic_expression = arithmetic_expression arithmetic_calculator.new_channel_color = [ color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0, ] arithmetic_calculator.new_channel_alpha = color.alpha() / 255.0 arithmetic_calculator.new_channel_name = ( self.new_channel_name_line_edit.text().strip()) arithmetic_calculator.slice_by_slice = ( self.slice_by_slice_checkbox.isChecked()) self.threadpool.start(arithmetic_calculator) else: self._error_function( "No action taken: arithmetic expression not set.") def __arithmetic_finished(self): self.num_threads_left = self.num_threads_left - 1 if self.num_threads_left == 0: QApplication.restoreOverrideCursor() self.status_bar.clearMessage() for i in range(self.progress_grid_layout.rowCount()): self.progress_grid_layout.itemAtPosition( i, 1).widget().setValue(0) # Enable the UI interaction after computation self.arithmetic_expression_text_edit.setReadOnly(False) self.slice_by_slice_checkbox.setEnabled(True) self.new_channel_name_line_edit.setReadOnly(False) self.new_channel_color_button.setEnabled(True) self.apply_button.setEnabled(True) self.processing_prev_button.setEnabled(True) # Inform the user that the calculations completed. If processing errors # occured then the desired operation may not have happened, but the # calculation was completed. QMessageBox().information(self, "Message", "Calculation completed.") self.processing_error = False
class GroupNwbfile(CollapsibleBox): def __init__(self, parent, metadata): """Groupbox for NWBFile fields filling form.""" super().__init__(title="NWBFile", parent=parent) self.parent = parent self.metadata = metadata #self.setTitle('NWBFile') self.group_type = 'NWBFile' self.groups_list = [] self.grid = QGridLayout() self.grid.setColumnStretch(2, 1) self.grid.setColumnStretch(4, 1) self.lbl_session_description = QLabel( 'session_description<span style="color:' + required_asterisk_color + ';">*</span>:') self.form_session_description = QLineEdit("session_description") self.form_session_description.setToolTip( "A description of the session where this data was generated") self.grid.addWidget(self.lbl_session_description, 0, 0, 1, 2) self.grid.addWidget(self.form_session_description, 0, 2, 1, 4) self.lbl_identifier = QLabel('identifier<span style="color:' + required_asterisk_color + ';">*</span>:') self.form_identifier = QLineEdit("ABC123") self.form_identifier.setToolTip( "a unique text identifier for the file") self.grid.addWidget(self.lbl_identifier, 1, 0, 1, 2) self.grid.addWidget(self.form_identifier, 1, 2, 1, 4) self.lbl_session_start_time = QLabel( 'session_start_time<span style="color:' + required_asterisk_color + ';">*</span>:') self.form_session_start_time1 = QLineEdit("") self.form_session_start_time1.setPlaceholderText("dd/mm/yyyy") self.form_session_start_time1.setToolTip( "the start date and time of the recording session") self.form_session_start_time2 = QLineEdit("") self.form_session_start_time2.setPlaceholderText("hh:mm") self.form_session_start_time2.setToolTip( "the start date and time of the recording session") self.grid.addWidget(self.lbl_session_start_time, 2, 0, 1, 2) self.grid.addWidget(self.form_session_start_time1, 2, 2, 1, 2) self.grid.addWidget(self.form_session_start_time2, 2, 4, 1, 2) self.lbl_experimenter = QLabel('experimenter:') self.form_experimenter = QLineEdit('') self.form_experimenter.setPlaceholderText( "Alan Lloyd Hodgkin, Andrew Fielding Huxley") self.form_experimenter.setToolTip( "Comma-separated list of names of persons who performed experiment" ) nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_experimenter, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_experimenter, nWidgetsGrid, 2, 1, 4) self.lbl_experiment_description = QLabel('experiment_description:') self.form_experiment_description = QLineEdit('') self.form_experiment_description.setPlaceholderText( "propagation of action potentials in the squid giant axon") self.form_experiment_description.setToolTip( "general description of the experiment") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_experiment_description, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_experiment_description, nWidgetsGrid, 2, 1, 4) self.lbl_session_id = QLabel('session_id:') self.form_session_id = QLineEdit('') self.form_session_id.setPlaceholderText("LAB 0123") self.form_session_id.setToolTip("lab-specific ID for the session") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_session_id, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_session_id, nWidgetsGrid, 2, 1, 4) self.lbl_institution = QLabel('institution:') self.form_institution = QLineEdit('') self.form_institution.setPlaceholderText("institution") self.form_institution.setToolTip( "institution(s) where experiment is performed") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_institution, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_institution, nWidgetsGrid, 2, 1, 4) self.lbl_lab = QLabel("lab:") self.form_lab = QLineEdit('') self.form_lab.setPlaceholderText("lab name") self.form_lab.setToolTip("lab where experiment was performed") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_lab, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_lab, nWidgetsGrid, 2, 1, 4) if 'lab_meta_data' in metadata.keys(): self.lbl_lab_meta_data = QLabel("lab_meta_data:") self.lab_meta_data = GroupCustomExtension( parent=self, metadata=metadata['lab_meta_data']) self.lab_meta_data.setToolTip( "an extension that contains lab-specific meta-data") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_lab_meta_data, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.lab_meta_data, nWidgetsGrid, 2, 1, 4) self.lbl_keywords = QLabel('keywords:') self.form_keywords = QLineEdit('') self.form_keywords.setPlaceholderText( "action potential, ion channels, mathematical model") self.form_keywords.setToolTip( "comma-separated list of terms to search over") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_keywords, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_keywords, nWidgetsGrid, 2, 1, 4) self.lbl_notes = QLabel("notes:") self.form_notes = QLineEdit('') self.form_notes.setPlaceholderText("") self.form_notes.setToolTip("Notes about the experiment") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_notes, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_notes, nWidgetsGrid, 2, 1, 4) self.lbl_pharmacology = QLabel("pharmacology:") self.form_pharmacology = QLineEdit('') self.form_pharmacology.setPlaceholderText("") self.form_pharmacology.setToolTip( "Description of drugs used, including how and when they were administered.\n" "Anesthesia(s), painkiller(s), etc., plus dosage, concentration, etc." ) nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_pharmacology, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_pharmacology, nWidgetsGrid, 2, 1, 4) self.lbl_protocol = QLabel("protocol:") self.form_protocol = QLineEdit('') self.form_protocol.setPlaceholderText("") self.form_protocol.setToolTip( "Experimental protocol, if applicable. E.g. include IACUC protocol" ) nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_protocol, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_protocol, nWidgetsGrid, 2, 1, 4) self.lbl_related_publications = QLabel("related publications:") self.form_related_publications = QLineEdit('') self.form_related_publications.setPlaceholderText("") self.form_related_publications.setToolTip( "Publication information. PMID, DOI, URL, etc. If multiple, concatenate " "together \nand describe which is which") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_related_publications, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_related_publications, nWidgetsGrid, 2, 1, 4) self.lbl_slices = QLabel("slices:") self.form_slices = QLineEdit('') self.form_slices.setPlaceholderText("") self.form_slices.setToolTip( "Description of slices, including information about preparation thickness," "\norientation, temperature and bath solution") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_slices, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_slices, nWidgetsGrid, 2, 1, 4) self.lbl_data_collection = QLabel("data_collection:") self.form_data_collection = QLineEdit('') self.form_data_collection.setPlaceholderText("") self.form_data_collection.setToolTip( "Notes about data collection and analysis") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_data_collection, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_data_collection, nWidgetsGrid, 2, 1, 4) self.lbl_surgery = QLabel("surgery:") self.form_surgery = QLineEdit('') self.form_surgery.setPlaceholderText("") self.form_surgery.setToolTip( "Narrative description about surgery/surgeries, including date(s) and who performed surgery." ) nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_surgery, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_surgery, nWidgetsGrid, 2, 1, 4) self.lbl_virus = QLabel("virus:") self.form_virus = QLineEdit('') self.form_virus.setPlaceholderText("") self.form_virus.setToolTip( "Information about virus(es) used in experiments, including virus ID, source, " "date made, injection location, volume, etc.") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_virus, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_virus, nWidgetsGrid, 2, 1, 4) self.lbl_stimulus_notes = QLabel("stimulus_notes:") self.form_stimulus_notes = QLineEdit('') self.form_stimulus_notes.setPlaceholderText("") self.form_stimulus_notes.setToolTip( "Notes about stimuli, such as how and where presented.") nWidgetsGrid = self.grid.rowCount() self.grid.addWidget(self.lbl_stimulus_notes, nWidgetsGrid, 0, 1, 2) self.grid.addWidget(self.form_stimulus_notes, nWidgetsGrid, 2, 1, 4) #self.setLayout(self.grid) self.setContentLayout(self.grid) def read_fields(self): """Reads fields and returns them structured in a dictionary.""" error = None data = {} data['session_description'] = self.form_session_description.text() data['identifier'] = self.form_identifier.text() str_datetime = self.form_session_start_time1.text( ) + ", " + self.form_session_start_time2.text() try: data['session_start_time'] = datetime.strptime( str_datetime, '%d/%m/%Y, %H:%M') except Exception as error: self.parent.write_to_logger(str(error)) self.parent.write_to_logger( "ERROR: Invalid 'session_start_time' format. " "Please fill in correct format.") return None, error if self.form_experimenter.text() != '': data['experimenter'] = self.form_experimenter.text() else: data['experimenter'] = None if self.form_experiment_description.text() != '': data[ 'experiment_description'] = self.form_experiment_description.text( ) else: data['experiment_description'] = None if self.form_session_id.text() != '': data['session_id'] = self.form_session_id.text() else: data['session_id'] = None if self.form_institution.text() != '': data['institution'] = self.form_institution.text() else: data['institution'] = None if self.form_lab.text() != '': data['lab'] = self.form_lab.text() else: data['lab'] = None if 'lab_meta_data' in self.metadata.keys(): data['lab_meta_data'], error = self.lab_meta_data.read_fields() if len(self.form_keywords.text()) > 0: keywords = self.form_keywords.text() data['keywords'] = [kw.strip() for kw in keywords.split(',')] else: data['keywords'] = None if self.form_notes.text() != '': data['notes'] = self.form_notes.text() else: data['notes'] = None if self.form_pharmacology.text() != '': data['pharmacology'] = self.form_pharmacology.text() else: data['pharmacology'] = None if self.form_protocol.text() != '': data['protocol'] = self.form_protocol.text() else: data['protocol'] = None if self.form_related_publications.text() != '': data['related_publications'] = self.form_related_publications.text( ) else: data['related_publications'] = None if self.form_slices.text() != '': data['slices'] = self.form_slices.text() else: data['slices'] = None if self.form_data_collection.text() != '': data['data_collection'] = self.form_data_collection.text() else: data['data_collection'] = None if self.form_surgery.text() != '': data['surgery'] = self.form_surgery.text() else: data['surgery'] = None if self.form_virus.text() != '': data['virus'] = self.form_virus.text() else: data['virus'] = None if self.form_stimulus_notes.text() != '': data['stimulus_notes'] = self.form_stimulus_notes.text() else: data['stimulus_notes'] = None return data, error def write_fields(self, data={}): """Reads structured dictionary and write in form fields.""" self.form_session_description.setText(data['session_description']) self.form_identifier.setText(data['identifier']) if 'session_start_time' in data and data['session_start_time']: str_datetime = data['session_start_time'].strftime( '%d/%m/%Y, %H:%M') self.form_session_start_time1.setText(str_datetime.split(',')[0]) self.form_session_start_time2.setText( str_datetime.split(',')[1].strip()) if 'experimenter' in data and data['experimenter'] is not None: if isinstance(data['experimenter'], list): self.form_experimenter.setText(','.join( str(x) for x in data['experimenter'])) else: self.form_experimenter.setText(data['experimenter']) if 'experiment_description' in data: self.form_experiment_description.setText( data['experiment_description']) if 'session_id' in data: self.form_session_id.setText(data['session_id']) if 'institution' in data: self.form_institution.setText(data['institution']) if 'lab' in data: self.form_lab.setText(data['lab']) if 'keywords' in data and data['keywords'] is not None: self.form_keywords.setText(','.join( str(x) for x in data['keywords'])) if 'notes' in data: self.form_notes.setText(data['notes']) if 'pharmacology' in data: self.form_pharmacology.setText(data['pharmacology']) if 'protocol' in data: self.form_protocol.setText(data['protocol']) if 'related_publications' in data: self.form_related_publications.setText( data['related_publications']) if 'slices' in data: self.form_slices.setText(data['slices']) if 'data_collection' in data: self.form_data_collection.setText(data['data_collection']) if 'surgery' in data: self.form_surgery.setText(data['surgery']) if 'virus' in data: self.form_virus.setText(data['virus']) if 'stimulus_notes' in data: self.form_stimulus_notes.setText(data['stimulus_notes'])
class SequenceRecordsWindow(QWidget): def __init__(self, parent): super(SequenceRecordsWindow, self).__init__(parent) self.grid_layout = QGridLayout() self.grid_layout.setContentsMargins(0, 0, 0, 0) self.grid_layout.setSpacing(0) self.setLayout(self.grid_layout) self.seq_font = QFont() self.seq_font.setFamily("Noto Sans Mono") self.seq_font.setPointSize(12) self.seq_font.setFixedPitch(True) self.seq_font.setStyleHint(QFont.Monospace) self.seq_h_scroll_bar = QScrollBar(self, self.parent()) self.seq_h_scroll_bar.setOrientation(Qt.Horizontal) self.seq_h_scroll_bar.setMinimum(0) self.seq_h_scroll_bar.setMaximum(self.longest_seq_len - self.char_nb) self.seq_h_scroll_bar.valueChanged.connect(self.move_seqs) self.grid_layout.addWidget(self.seq_h_scroll_bar, self.grid_layout.rowCount(), 5) self.lower_spacer_item = QSpacerItem(1, 1, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding) self.grid_layout.addItem(self.lower_spacer_item) self.seq_record_items = [] def sizeHint(self): # Workaroud QTBUG-70305 return self.parent().parent().size() def populate(self, seq_records): self.grid_layout.removeWidget(self.seq_h_scroll_bar) self.grid_layout.removeItem(self.lower_spacer_item) for seq_record in seq_records: new_row = self.grid_layout.rowCount() self.seq_record_items.append( SequenceRecordItem(self, seq_record, self.seq_font)) for widget_index in range(0, len(self.seq_record_items[-1].widgets)): col = widget_index self.seq_record_items[-1].seqLabel.installEventFilter(self) self.grid_layout.addWidget( self.seq_record_items[-1].widgets[widget_index], new_row, col) if len(seq_record) > self.longest_seq_len: self.longest_seq_len = len(seq_record) self.update_char_nb() self.grid_layout.addWidget(self.seq_h_scroll_bar, self.grid_layout.rowCount(), 5) self.grid_layout.addItem(self.lower_spacer_item) self.display_all_seq() def clear(self): # TODO pass def eventFilter(self, watched, event): if event.type() == QEvent.Resize: self.update_char_nb() self.update_scrollbar() self.display_all_seq() return super(SequenceRecordsWindow, self).eventFilter(watched, event) def display_all_seq(self): for seq_record_item in self.seq_record_items: seq_record_item.seqLabel.display_seq( seq_record_item.seq_record.seq, self.display_begin, self.char_nb) def update_char_nb(self): font_metrics = QFontMetrics(self.seq_font) px_wide_char = font_metrics.width("A") label_width = self.seq_record_items[0].seqLabel.width( ) # width of first seq label = all seq labels approx_char_nb = label_width // px_wide_char test_str = "A" * approx_char_nb while font_metrics.width( test_str) < label_width: # fontMetrics not precise at all... test_str += "A" while font_metrics.width( test_str) >= label_width: # fontMetrics not precise at all... test_str = test_str[:-1] self.char_nb = len(test_str) def update_scrollbar(self): self.seq_h_scroll_bar.setMaximum(self.longest_seq_len - self.char_nb + 12) def move_seqs(self, value): print(value) self.display_begin = value self.display_all_seq() char_nb = 0 longest_seq_len = 0 display_begin = 0
class NetworkDevice(): def __init__(self, jsonDict, warningHandler: WarningHandler, tabWidget: QTabWidget): """ Base class for any object on the MQTT network """ self.ipAddr = jsonDict['ip'] self.mac = jsonDict['mac'] self.name = jsonDict['name'] self.apiVersion = jsonDict['api'] self.warningHandler = warningHandler self.uptime = "" self.linkSpeed = jsonDict['link'] self._tabWidget = tabWidget self.updated = False # Flag if any info has been updated self.warnings = [] self.errors = [] self.state = None # Create tab in the tab widget for this device self._tab = QWidget(self._tabWidget) if (self.mac == get_mac()): # Ensure that the controller in use goes first self._tabWidget.insertTab(0, self._tab, f"{self.name}\n{self.ipAddr}") else: # Other device, put at end self._tabWidget.addTab(self._tab, f"{self.name}\n{self.ipAddr}") self._horizontalLayout = QHBoxLayout(self._tab) self._grid = QGridLayout() self._iconLayout = QVBoxLayout() self._horizontalLayout.addLayout(self._grid) self._horizontalLayout.addLayout(self._iconLayout) self._horizontalLayout.setStretchFactor(self._iconLayout, 1) self._ipLabel = self.add_value_row("IP Address:", self.ipAddr) # These are the definition of a unique device so never need updating self.add_value_row("Type:", self.get_type()) self.add_value_row("MAC Address:", self.mac) self._apiLabel = self.add_value_row("MQTT API Version:", self.apiVersion) self._uptimeLabel = self.add_value_row("Uptime:", self.uptime) self._onlineLabel = self.add_value_row("Online:", "") self._update_online_state(True) self._linkSpeedLabel = self.add_value_row( "Link Speed:", str(self.linkSpeed) + " Mbps") self._grid.setRowStretch(self._grid.rowCount(), 1) self._tab.setLayout(self._horizontalLayout) def add_value_row(self, labelString, initialValue): """ Adds a row to self._tab with two QLabels horizontally laid out. First contains the identifying string, second contains value. Args: labelString (str): Label for this item intialValue (str / int): value for this item Returns: QLabel: label item created for the value so it can be updated Raises: None """ labelWidget = QLabel(labelString) labelWidget.setStyleSheet( "font: Waree; font-size: 32px; font-weight: bold") valueWidget = QLabel(str(initialValue)) valueWidget.setStyleSheet("font: Waree; font-size: 32px") rowCount = self._grid.rowCount() # Should just be disable stretch on last row but seems # to need to be last two rows. Can't be bothered fixing self._grid.setRowStretch(rowCount - 1, 0) self._grid.setRowStretch(rowCount - 2, 0) self._grid.addWidget(labelWidget, rowCount, 0) self._grid.addWidget(valueWidget, rowCount, 1) self._grid.setRowStretch(self._grid.rowCount(), 1) return valueWidget def update_discovery_info(self, jsonDict: dict) -> None: """ Updates the discovery information for an already existing entry in self._tab. None of this should be changing - discovery information is intended to be static """ self._update_online_state(True) if self.ipAddr != jsonDict['ip']: self.warningHandler.add_warning( self.name, "Network", f"Device {self.name} ({self.mac}) changed IP address" f"from {self.ipAddr} to {jsonDict['ip']}") self.ipAddr = jsonDict['ip'] self.updated = True if self.name != jsonDict['name']: self.warningHandler.add_warning( self.name, "Network", f"Device {self.name} ({self.mac}) changed name" f"from {self.name} to {jsonDict['name']}") self.name = jsonDict['name'] self.updated = True if self.apiVersion != jsonDict['api']: self.warningHandler.add_warning( self.name, "Network", f"Device {self.name} ({self.mac}) changed API" f"from {self.apiVersion} to {jsonDict['api']}") self.apiVersion = jsonDict['api'] self.updated = True if self.linkSpeed != jsonDict['link']: self.warningHandler.add_warning( self.name, "Network", f"Device {self.name} ({self.mac}) changed speed" f"from {self.linkSpeed}Mbps to {jsonDict['link']} Mbps") self.name = jsonDict['name'] self.updated = True self._ipLabel.setText(self.ipAddr) self._apiLabel.setText(self.apiVersion) self._linkSpeedLabel.setText(str(self.linkSpeed) + " Mbps") # Online is a bit special as we want a tick box self._update_online_state(True) # Update tab name self._tabWidget.setTabText(self._tabWidget.indexOf(self._tab), f"{self.name}\n{self.ipAddr}") if self.updated: logging.info(f"Updated discovery info for device " f"{self.mac} ({self.name})") def update_status_info(self, jsonDict: dict) -> None: """ Updates the discovery information for an already existing entry in self._tab. """ # Online is a bit special as we want a tick box self._update_online_state(True) self._uptimeLabel.setText(jsonDict['uptime']) if self.updated: logging.debug(f"Updated status info for device " f"{self.mac} ({self.name})") def _update_online_state(self, online: bool): """ Updates online status icon """ if not isinstance(online, bool): raise TypeError(f"{online} not in (True, False)") self.online = online if online: icon = QIcon("resources/img/icon_tick.png") else: icon = QIcon("resources/img/icon_cross.png") pixmap = icon.pixmap(QSize(32, 32)) self._onlineLabel.setPixmap(pixmap)
else: controller = None modbusUpdateTimer.stop() graphUpdateTimer.stop() connectButton = QPushButton("Connect") connectButton.setCheckable(True) connectButton.setFont(appFont) connectButton.toggled.connect(connectToPort) tab1Layout = QGridLayout() actionLayout = QVBoxLayout() portSelectLayout = QHBoxLayout() tab1Layout.columnCount = 2 tab1Layout.rowCount = 2 tab1Layout.setColumnStretch(0, 2) tab1Layout.setColumnStretch(1, 1) # actionLayout.setSizeConstraint() actionLayout.addWidget(startStopButton) portSelectLayout.setStretch(0, 1) portSelectLayout.addWidget(refreshPortsButton) portSelectLayout.setStretch(1, 4) portSelectLayout.addWidget(portSelectBox) portSelectLayout.setStretch(2, 2) portSelectLayout.addWidget(connectButton) actionLayout.addLayout(portSelectLayout) tab1Layout.addWidget(graph, 0, 0) tab1Layout.addLayout(actionLayout, 0, 1)