def __init__(self, parent=None, gui_settings=None, config=None, **kw): QWidget.__init__(self, parent=parent) default_config = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'core', 'default_config.json') # if config: Variables().read(config) else: Variables().read(default_config) # tabWidget = TabWidget(gui_settings) footer = Footer(self, gui_settings=gui_settings) layout = QVBoxLayout(self) self.setLayout(layout) self.layout().setAlignment(QtCore.Qt.AlignTop) self.layout().addWidget(tabWidget) self.layout().addWidget(footer) self.setObjectName("MainWidget")
def setUp(self): """ Create the popup""" default_config_path = get_default_config_path() Variables().read(default_config_path) fin = open(default_config_path, 'r') self.defaults = json.load(fin)["res_evo_settings"] fin.close() self.popup = ResidueEvolutionPopup() self.variable_keys = tuple(self.popup.variables.keys()) self.local_variable_keys = tuple(self.popup.local_variables.keys())
def setUp(self): """ Create the popup""" default_config_path = get_default_config_path() Variables().read(default_config_path) fin = open(default_config_path, 'r') self.defaults = json.load(fin)["bar_plot_settings"] fin.close() self.popup = BarPlotPopup() self.variable_keys = tuple(self.popup.variables.keys()) self.local_variable_keys = tuple(self.popup.local_variables.keys())
def setUp(self): """ Create the popup""" default_config_path = get_default_config_path() Variables().read(default_config_path) fin = open(default_config_path, 'r') self.defaults = json.load(fin)["pre_files"] fin.close() self.popup = PRETheoreticalSelectionPopup() self.variable_keys = tuple(self.popup.variables.keys()) self.local_variable_keys = tuple(self.popup.local_variables.keys())
def setUp(self): """ Create the popup""" default_config_path = get_default_config_path() Variables().read(default_config_path) fin = open(default_config_path, 'r') self.defaults = json.load(fin)["csp_settings"]["csp_res_exceptions"] fin.close() self.popup = CSPExceptionsPopup() self.variable_keys = tuple(self.popup.variables.keys()) self.local_variable_keys = tuple(self.popup.local_variables.keys())
class BasePopup(QDialog): """ Base QDialog for all Farseer-NMR popups. Parameters: parent(QWidget): parent QWidget settings_keys(str or list): specifies the keys to access popup specific settings in variables. title(str): name of the popup. Methods: .launch() """ variables = Variables()._vars def __init__(self, parent, settings_key=None, title=None, layout='grid', **kw): QDialog.__init__(self, parent) self.setWindowTitle(title) if layout == 'grid': grid = QGridLayout() grid.setAlignment(QtCore.Qt.AlignTop) self.setLayout(grid) if layout == 'vbox': v_layout = QVBoxLayout() v_layout.setAlignment(QtCore.Qt.AlignTop) self.setLayout(v_layout) if settings_key: if isinstance(settings_key, str): self.local_variables = self.variables[settings_key] self.defaults = defaults[settings_key] elif isinstance(settings_key, list): self.local_variables = get_nested_value( self.variables, settings_key) self.defaults = get_nested_value(defaults, settings_key) def launch(self): self.exec_() self.raise_()
def setUp(self): ''' Create the popup''' default_config_path = get_default_config_path() Variables().read(default_config_path) fin = open(default_config_path, 'r') self.defaults = json.load(fin) fin.close() screen_resolution = app.desktop().screenGeometry() gui_settings, stylesheet = gui_utils.deliver_settings( screen_resolution) tabWidget = TabWidget(gui_settings) self.widget = Settings(tabWidget, gui_settings=gui_settings, footer=True) self.variable_keys = tuple(self.widget.variables.keys())
def load_config(self, path=None): """ Connection from Tab footer to TabWidget for loading Farseer-NMR configuration files. """ if not path: fname = QFileDialog.getOpenFileName(None, 'Load Configuration', os.getcwd()) else: fname = [path] if fname[0]: if fname[0].split('.')[1] == 'json': Variables().read(fname[0]) self.load_variables() self.config_file = fname[0] return
class BaseWidget(QWidget): """ Forms the base of all Farseer-NMR widgets. The tabs inside the Farseer GUI are all subclasses of this class, which ensures that an instance of the variables "Borg" class is automatically available as self.variables without having to import it into individual widgets. The gui_settings argument is passed in when the subclassed widget is instantiated in TabWidget and ensures that all design and styling are applied to all GUI features. The footer boolean is used to switch on and off the load, save and run buttons at the bottom of a widget. This can be useful if a widget is particularly space hungry. We strongly advocate that all additional widgets inherit from this base class! Parameters: parent (QWidget): gui_settings (dict): a dictionary carrying the settings required to correctly render the graphics based on screen resolution. footer (boolean): determines whether the footer will be displayed. """ variables = Variables()._vars def __init__(self, parent=None, gui_settings=None, footer=True): QWidget.__init__(self, parent=parent) self.gui_settings = gui_settings if footer: self.tab_footer = TabFooter(self) self.tab_footer.load_config_button.clicked.connect( parent.load_config) self.tab_footer.save_config_button.clicked.connect( parent.save_config) self.tab_footer.run_farseer_button.clicked.connect( parent.run_farseer_calculation)
def save_config(self, path=None): """ Connection from Tab footer to TabWidget for saving Farseer-NMR configuration files. """ self.interface.save_config() if not path: filters = "JSON files (*.json)" selected_filter = "JSON files (*.json)" fname = QFileDialog.getSaveFileName(self, "Save Configuration", "", filters, selected_filter) else: fname = [path] if not fname[0].endswith('.json'): fname = [fname[0] + ".json"] if fname[0]: with open(fname[0], 'w') as outfile: Variables().write(outfile) self.config_file = os.path.abspath(fname[0]) print('Configuration saved to {}'.format(fname[0]))
class PeakListArea(QWidget): """ A widget containing a QGraphicsScene for rendering the drag and drop creation of the Farseer-NMR cube. When a peaklist is dropped on a PeakListLabel instance, the variables instance is updated and its position in the Farseer-NMR cube is specified. Parameters: parent (QWidget): specifies the parent widget containing the QLabel and the QSpinBox. gui_settings (dict): a dictionary carrying the settings required to correctly render the graphics based on screen resolution. Methods: .setEvents() .side_bar() .update_variables() .show_update_warning() .show_duplicate_key_warning() .update_experimental_dataset(values_dict) .check_conditions_for_tree() .update_tree() .add_connecting_line() """ variables = Variables()._vars def __init__(self, parent, gui_settings): QWidget.__init__(self, parent) self.scene = QGraphicsScene(self) self.height = gui_settings['peaklistarea_height'] self.scrollContents = QGraphicsView(self.scene, self) self.scrollContents.setRenderHint(QtGui.QPainter.Antialiasing) self.scene.setSceneRect(0, 0, width, self.height) layout = QGridLayout() self.setLayout(layout) self.layout().addWidget(self.scrollContents) self.scrollContents.setMinimumSize(gui_settings['scene_width'], gui_settings['scene_height']) self.scrollContents.setAcceptDrops(True) self.set_events() self.updateClicks = 0 def set_events(self): self.scrollContents.scene().dragEnterEvent = self._dragEnterEvent def _dragEnterEvent(self, event): event.accept() def side_bar(self): return self.parent().parent().parent().side_bar def update_variables(self): self.update_tree() def show_update_warning(self): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Reset Experimental Series") msg.setInformativeText("Do you want to all peaklists from the " "Experimental Series and re-draw the series?") msg.setWindowTitle("Reset Experimental Series") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) retval = msg.exec_() return retval def show_duplicate_key_warning(self, axis): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Duplicate conditions") msg.setInformativeText("There are duplicate " "conditions on the {} axis. ".format(axis)) msg.setWindowTitle("Duplicate conditions") msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_() return retval def show_empty_condition_warning(self): msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Empty conditions") msg.setInformativeText("There are empty conditions, " "so dataset cannot be drawn.") msg.setWindowTitle("Empty conditions") msg.setStandardButtons(QMessageBox.Ok) retval = msg.exec_() return retval def update_experimental_dataset(self, values_dict): tmp_dict = {} for z in values_dict["z"]: tmp_dict[z] = {} for y in values_dict["y"]: tmp_dict[z][y] = {} for x in values_dict["x"]: tmp_dict[z][y][x] = '' if self.variables["experimental_dataset"][z][y].keys(): tmp_dict[z][y][x] = \ self.variables["experimental_dataset"][z][y][x] return tmp_dict def check_conditions_for_tree(self): self.values_dict = self.variables["conditions"] # Check if condition boxes are empty and throw warning if so. if not all(x for v in self.values_dict.values() for x in v): self.show_empty_condition_warning() return False if len(set(self.values_dict['z'])) != len(self.values_dict['z']): self.show_duplicate_key_warning('z') return False if len(set(self.values_dict['y'])) != len(self.values_dict['y']): self.show_duplicate_key_warning('y') return False if len(set(self.values_dict['x'])) != len(self.values_dict['x']): self.show_duplicate_key_warning('x') return False return True def update_tree(self): if not self.check_conditions_for_tree(): return if self.updateClicks > 0: if self.show_update_warning() == QMessageBox.Cancel: return self.peak_list_objects = [] self.peak_list_dict = {} self.show() self.side_bar().refresh_sidebar() self.scene.clear() z_conds = self.values_dict['z'] y_conds = self.values_dict['y'] x_conds = self.values_dict['x'] num_x = len(x_conds) num_y = len(y_conds) num_z = len(z_conds) total_x = num_x * num_y * num_z keys_to_remove = [ k for k in self.variables['fasta_files'].keys() if k not in y_conds ] for k in keys_to_remove: del self.variables['fasta_files'][k] if total_x > 10: self.scrollContents.setSceneRect(0, 0, width, total_x * 22) else: self.scrollContents.setSceneRect(0, 0, width, self.height) self.scrollContents.fitInView(0, 0, width, self.height, QtCore.Qt.KeepAspectRatio) if total_x < 2: x_spacing = self.scene.height() / 2 elif 2 < total_x < 10: x_spacing = self.scene.height() / (total_x + 1) else: x_spacing = 20 zz_pos = 0 yy_pos = self.scene.width() * 0.25 xx_pos = self.scene.width() * 0.5 pl_pos = self.scene.width() * 0.75 xx_vertical = x_spacing num = 0 for i, z in enumerate(z_conds): self.peak_list_dict[z] = {} y_markers = [] for j, y in enumerate(y_conds): self.peak_list_dict[z][y] = {} x_markers = [] for k, x in enumerate(x_conds): xx = ConditionLabel(str(x), [xx_pos, xx_vertical]) self.scene.addItem(xx) if z not in self.variables["experimental_dataset"].keys() \ or y not in self.variables["experimental_dataset"][z].keys() \ or x not in self.variables["experimental_dataset"][z][y].keys(): pl = PeakListLabel(self, 'Drop peaklist here', self.scene, [pl_pos, xx_vertical], x_cond=x, y_cond=y, z_cond=z) self.peak_list_dict[z][y][x] = '' elif not self.variables["experimental_dataset"][z][y][x]: pl = PeakListLabel(self, 'Drop peaklist here', self.scene, [pl_pos, xx_vertical], x_cond=x, y_cond=y, z_cond=z) self.peak_list_dict[z][y][x] = '' else: pl_name = self.variables["experimental_dataset"][z][y][ x] pl = PeakListLabel(self, pl_name, self.scene, [pl_pos, xx_vertical], x_cond=x, y_cond=y, z_cond=z, peak_list=pl_name) self.peak_list_dict[z][y][x] = pl_name self.side_bar()._raise_context_menu(pl_name) self.peak_list_objects.append(pl) self.scene.addItem(pl) self.add_connecting_line(xx, pl) x_markers.append(xx) num += 1 xx_vertical += x_spacing if len(x_markers) % 2 == 1: yy_vertical = x_markers[int(math.ceil(len(x_markers)) / 2)].y() else: yy_vertical = x_markers[int( math.ceil(len(x_markers)) / 2)].y() - (x_spacing / 2) yy = ConditionLabel(str(y), [yy_pos, yy_vertical]) y_markers.append(yy) self.scene.addItem(yy) for x_marker in x_markers: self.add_connecting_line(yy, x_marker) if len(y_markers) % 2 == 1: zz_vertical = y_markers[int(math.ceil(len(y_markers)) / 2)].y() else: zz_vertical = (y_markers[0].y() + y_markers[-1].y()) / 2 zz = ConditionLabel(str(z), [zz_pos, zz_vertical]) self.scene.addItem(zz) for x_marker in y_markers: self.add_connecting_line(zz, x_marker) self.updateClicks += 1 self.variables["experimental_dataset"] = self.peak_list_dict def add_connecting_line(self, atom1, atom2): if atom1.y() > atom2.y(): y1 = atom1.y() + (atom1.boundingRect().height() * .5) y2 = atom2.y() + (atom2.boundingRect().height() * .5) elif atom1.y() < atom2.y(): y1 = atom1.y() + (atom1.boundingRect().height() * .5) y2 = atom2.y() + (atom2.boundingRect().height() * .5) else: y1 = atom1.y() + (atom1.boundingRect().height() * 0.5) y2 = atom2.y() + (atom2.boundingRect().height() * 0.5) if atom1.x() > atom2.x(): x1 = atom1.x() x2 = atom2.x() + atom2.boundingRect().width() elif atom1.x() < atom2.x(): x1 = atom1.x() + atom1.boundingRect().width() x2 = atom2.x() else: x1 = atom1.x() + (atom1.boundingRect().width() / 2) x2 = atom2.x() + (atom1.boundingRect().width() / 2) new_line = QGraphicsLineItem(x1, y1, x2, y2) pen = QtGui.QPen() pen.setColor(QtGui.QColor("#FAFAF7")) pen.setCosmetic(True) pen.setWidth(1) new_line.setPen(pen) self.scene.addItem(new_line)
class TabWidget(QTabWidget): """ The container for all tab widgets in the Farseer-NMR GUI. To add a new tab to the tab widget, the QWidget class of the needs to be imported into this file. The instantiation of the class and the addition of the QWidget to the TabWidget need to be coded in the add_tabs_to_widget method. Parameters: gui_settings (dict): a dictionary carrying the settings required to correctly render the graphics based on screen resolution. Methods: .add_tabs_to_widget() .set_data_sets() .add_tab(QWidget, str, str) .load_config(str) .load_variables() .load_peak_lists(str) .save_config(str) .run_farseer_calculation """ variables = Variables()._vars def __init__(self, gui_settings): QTabWidget.__init__(self, parent=None) self.widgets = [] self.gui_settings = gui_settings self._add_tab_logo() self.add_tabs_to_widget() def add_tabs_to_widget(self): """ Create instances of all widget classes and add them to the TabWidget """ self.peaklist_selection = \ PeaklistSelection(self, gui_settings=self.gui_settings, footer=False) self.interface = Settings(self, gui_settings=self.gui_settings, footer=True) self.add_tab(self.peaklist_selection, "PeakList Selection") self.add_tab(self.interface, "Settings", "Settings") self.widgets.extend([self.peaklist_selection, self.interface]) def set_data_sets(self): """Set data in the tabs if they have data_sets as an attribute""" for widget in self.widgets: if hasattr(widget, 'set_data_sets'): widget.set_data_sets() def add_tab(self, widget, name, object_name=None): """Re-implemented of the addTab method to ensure proper compatibility with the stylesheet and architecture. """ tab = QWidget() tab.setLayout(QGridLayout()) tab.layout().addWidget(widget) self.addTab(tab, name) if object_name: tab.setObjectName(object_name) def load_config(self, path=None): """ Connection from Tab footer to TabWidget for loading Farseer-NMR configuration files. """ if not path: fname = QFileDialog.getOpenFileName(None, 'Load Configuration', os.getcwd()) else: fname = [path] if fname[0]: if fname[0].split('.')[1] == 'json': Variables().read(fname[0]) self.load_variables() self.config_file = fname[0] return def load_variables(self): """Load variables into self.variables instance.""" self.interface.load_variables() self.peaklist_selection.load_variables() self.peaklist_selection.side_bar.update_from_config() def load_peak_lists(self, path=None): """Load peaklists into sidebar. Called from self.load_variables.""" if os.path.exists(path): self.peaklist_selection.side_bar.load_from_path(path) def save_config(self, path=None): """ Connection from Tab footer to TabWidget for saving Farseer-NMR configuration files. """ self.interface.save_config() if not path: filters = "JSON files (*.json)" selected_filter = "JSON files (*.json)" fname = QFileDialog.getSaveFileName(self, "Save Configuration", "", filters, selected_filter) else: fname = [path] if not fname[0].endswith('.json'): fname = [fname[0] + ".json"] if fname[0]: with open(fname[0], 'w') as outfile: Variables().write(outfile) self.config_file = os.path.abspath(fname[0]) print('Configuration saved to {}'.format(fname[0])) def run_farseer_calculation(self): """ Executes Farseer-NMR calculation in its own thread. Saves configuration if not already saved. Performs necessary checks for execution. """ msg = QMessageBox() msg.setStandardButtons(QMessageBox.Ok) msg.setIcon(QMessageBox.Warning) if not all(x for x in self.variables["conditions"].values()): msg.setText('Experimental Series not set up correctly.') msg.setInformativeText("""Please ensure that all conditions in the Peaklist Tree have labels and that all X axis conditions have a peaklist associated.""") msg.exec_() return from core.Threading import Threading output_path = self.variables["general_settings"]["output_path"] run_msg = check_input_construction(output_path, self.variables) print(run_msg) if run_msg in ["Spectra", "Backbone", "Sidechains"]: msg.setText("{} Path Exists.".format(run_msg)) msg.setInformativeText( """{} folder already exists in Calculation Output Path. Calculation cannot be launched.""".format(run_msg)) msg.exec_() elif run_msg == "No dataset": msg.setText("No dataset configured.") msg.setInformativeText( "No Experimental dataset has been created. " "Please define an Experimental Dataset Tree and populate it \ with the corresponding peaklist files.") msg.exec_() elif run_msg == "FASTA file not provided": msg.setText("FASTA file not provided.") msg.setInformativeText("""The Apply FASTA box is activated. This calculation requires FASTA files to be specified for each Y axis condition.""") msg.exec_() elif run_msg == "No FASTA for peaklist": msg.setText("No FASTA for NmrDraw/NmrView peaklists") msg.setInformativeText("""You have input NmrView/NmrDraw peaklists. These require a FASTA file to be specified. Plase do so in FASTA menu. Refer to WET#26 for more details. """) msg.exec_() elif run_msg == "No populated Tree": msg.setText("Tree not completely populated.") msg.setInformativeText( "There are branches in the Experimental Tree which are \ not populated. Please ensure that all branches have a peaklist assigned.") msg.exec_() elif run_msg == "Para name not set": msg.setText("You have activated Do PRE Analysis") msg.setInformativeText( """When analysing paramagnetic data, the datapoint names of the Z axis must be exactly "dia" and "para" for diamagnetic and paramagnetic datasets, respectively. We appologise but other words are not accepted. Please correct the Z names accordingly. """) msg.exec_() elif run_msg == "PRE file not provided": msg.setText("PRE file not provided.") msg.setInformativeText("""The PRE Analysis box is activated. This calculation requires Theoretical PRE files to be specified for each Y axis condition.""") msg.exec_() elif run_msg == "Run": create_directory_structure(output_path, self.variables) #from core.farseermain import start_logger, read_user_variables, run_farseer from core.farseermain import run_farseer run_config_name = "user_config_{}.json".format( datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) config_path = os.path.join(output_path, run_config_name) self.save_config(path=config_path) Threading(function=run_farseer, args=[config_path]) else: print('Run could not be initiated') def _add_tab_logo(self): """Add logo to tab header.""" self.tablogo = QLabel(self) self.tablogo.setAutoFillBackground(True) self.tablogo.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter) pixmap = QtGui.QPixmap( os.path.join(ICON_DIR, 'icons', 'header-logo.png')) self.tablogo.setPixmap(pixmap) self.tablogo.setContentsMargins(9, 0, 0, 6) self.setCornerWidget(self.tablogo, corner=QtCore.Qt.TopLeftCorner) self.setFixedSize( QtCore.QSize(self.gui_settings['app_width'], self.gui_settings['app_height']))
class SideBar(QTreeWidget): """ A QTreeWidget that enables drag-and-drop loading and parsing of peaklists. Peaklist files are dropped onto the sidebar, their format is detected and they are loaded into Farseer-NMR identified by the file name. The labels in the sidebar point to in memory representations of the peaklists, which are parsed out when a calculation is executed. Parameters: parent (QWidget): gui_settings (dict): a dictionary carrying the settings required to correctly render the graphics based on screen resolution. Methods: .update_from_config() .dragEnterEvent(QMouseEvent) .dropEvent(QMouseEvent) .load_from_path(str) .refresh_sidebar() .load_peaklist(str) .add_item(str) ._raise_context_menu(str) """ variables = Variables()._vars def __init__(self, parent=None, gui_settings=None): QTreeWidget.__init__(self, parent) self.header().hide() self.setDragEnabled(True) self.setExpandsOnDoubleClick(False) self.setDragDropMode(self.InternalMove) self.acceptDrops() self.setMinimumWidth(200) self.setMaximumWidth(320) self.setFixedHeight(gui_settings['sideBar_height']) self.peakLists = self.variables['peaklists'] self.setSortingEnabled(True) self.update_from_config() def update_from_config(self): """Update sidebar contents based on configuration file.""" self.clear() used_peaklists = [] self.peakLists = self.variables["peaklists"] if not all(x for v in self.variables["conditions"].values() for x in v): self.refresh_sidebar() else: for z in self.variables["conditions"]["z"]: for y in self.variables["conditions"]["y"]: for x in self.variables["conditions"]["x"]: used_peaklists.append( self.variables["experimental_dataset"][z][y][x]) unused_peaklists = \ [x for x, pl in self.variables["peaklists"].items() if x not in used_peaklists] for peaklist in unused_peaklists: self.add_item(peaklist) def dragEnterEvent(self, event): """Re-implemenation for drag-and-drop behaviour.""" event.accept() if not event.mimeData().hasUrls(): item = self.itemAt(event.pos()) if not item: return text = item.text(0) event.mimeData().setText(text) def dropEvent(self, event): """Re-implemenation for drag-and-drop behaviour.""" if event.mimeData().hasUrls(): event.accept() file_paths = [url.path() for url in event.mimeData().urls()] for file_path in file_paths: self.load_from_path(file_path) def load_from_path(self, file_path): """load a peaklists from a directory path.""" name = None if os.path.isdir(file_path): for root, dirs, filenames in os.walk(file_path): for filename in filenames: try: path = os.path.join(root, filename) name, path = self.load_peaklist(path) except IOError: pass else: name, path = self.load_peaklist(file_path) if name: return name, path def refresh_sidebar(self): """ clear the sidebar and refresh the peaklist names.""" self.clear() for peaklist in self.peakLists.keys(): self.add_item(peaklist) def load_peaklist(self, file_path): """load individual peaklist from a file path.""" if os.path.isdir(file_path): return name = os.path.basename(file_path) if name not in self.peakLists.keys(): peaklist = read_peaklist(file_path) if peaklist: pl_name = name if peaklist[0].format_ in peaklist_format_requires_fasta: msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Peaklist with no residue type information") msg.setInformativeText( """This peaklist doesn't contain information residue types. Please ensure to select an approriate FASTA file in the correct Y axis condition. Refer to WET#26 for more details.""") msg.setWindowTitle("Duplicate conditions") msg.setStandardButtons(QMessageBox.Ok) msg.exec_() item = self.add_item(pl_name) self.peakLists[item.text(0)] = peaklist self.peakLists[pl_name] = file_path return pl_name, file_path else: #print("*** File not loaded into SideBar\n***") return None, None else: print('Peaklist with name %s already exists.' % name) return None, None def add_item(self, name): """Add a peaklist pointer to the side bar as a QTreeWidgetItem.""" newItem = QTreeWidgetItem(self) newItem.setFlags(newItem.flags() & ~QtCore.Qt.ItemIsDropEnabled) newItem.setText(0, name) self.sortByColumn(0, QtCore.Qt.AscendingOrder) return newItem def _raise_context_menu(self, item_name): """raise a context menu to enabled deletion of objects.""" import sip result = self.findItems(item_name, QtCore.Qt.MatchRecursive, 0) if result: sip.delete(result[0])