class AlarmTreeEditorDisplay(Display): def __init__(self, parent=None): super(AlarmTreeEditorDisplay, self).__init__(parent=parent) self.app = QApplication.instance() # set up the ui self.setup_ui() # allow add and remove row self.add_button.clicked.connect(self.insertChild) self.remove_button.clicked.connect(self.removeItem) self.remove_button.setEnabled(True) # connect save changes self.button_box.accepted.connect(self.save_property_changes) # upon tree view selection, change the item view self.tree_view.selectionModel().selectionChanged.connect( self.handle_selection) self.tree_view.tree_model.dataChanged.connect(self.item_change) self.file_dialog = QFileDialog() self.open_config_action = QAction("Open", self) self.open_config_action.triggered.connect(self.open_file) self.toolbar.addAction(self.open_config_action) self.save_config_action = QAction("Save", self) self.save_config_action.triggered.connect(self.save_configuration) self.toolbar.addAction(self.save_config_action) # update configuration name self.tree_label.editingFinished.connect(self._update_config_name) # default open size self.resize(800, 600) self.config_tool = PhoebusConfigTool() def setup_ui(self): self.main_layout = QGridLayout() self.setLayout(self.main_layout) # add toolbar self.toolbar = QToolBar() self.main_layout.setMenuBar(self.toolbar) # create the tree view layout and add/remove buttons self.tree_view_layout = QVBoxLayout() self.tree_view = PyDMAlarmTree(self, config_name="UNITITLED", edit_mode=True) self.tree_view.setEditTriggers(QAbstractItemView.DoubleClicked) self.tree_view.setSelectionMode(QAbstractItemView.SingleSelection) self.tree_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.tree_view.setHeaderHidden(True) # Drag/drop self.tree_view.setDragDropMode(QAbstractItemView.InternalMove) self.tree_view.setDragEnabled(True) self.tree_view.setAcceptDrops(True) # view sizing self.tree_view.setColumnWidth(0, 160) self.tree_view.setColumnWidth(1, 160) self.tree_view.setColumnWidth(2, 160) # lable for tree view configuration_indicator = QLabel("Configuration:") self.tree_label = QLineEdit("Untitled") self.tree_label_layout = QHBoxLayout() self.tree_label_layout.addWidget(configuration_indicator) self.tree_label_layout.addWidget(self.tree_label) self.tree_view_layout.addLayout(self.tree_label_layout) self.tree_view_layout.addWidget(self.tree_view) # add/ remove buttons self.add_remove_layout = QHBoxLayout() spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.add_remove_layout.addItem(spacer) self.add_button = QPushButton("New", self) self.add_remove_layout.addWidget(self.add_button) self.remove_button = QPushButton("Remove", self) self.add_remove_layout.addWidget(self.remove_button) self.tree_view_layout.addLayout(self.add_remove_layout) # add the tree view to the window self.main_layout.addLayout(self.tree_view_layout, 0, 0) self.property_layout = QVBoxLayout() self.property_layout.addWidget(QLabel("Alarm Properties")) # crate property view self.property_data_layout = QStackedLayout() self.property_layout.addLayout(self.property_data_layout) self.property_widget_config = QWidget() self.property_widget_config.setWindowTitle("config") # create group widget self.property_widget_group = QWidget() self.property_widget_group.setWindowTitle("group") self.property_view_layout_group = QGridLayout() # add label self.label_edit_group = QLineEdit() self.label_label_group = QLabel("NAME") # add guidance self.guidance_edit_group = QLineEdit() self.guidance_label_group = QLabel("GUIDANCE") self.property_view_layout_group.addWidget(self.label_label_group, 1, 0) self.property_view_layout_group.addWidget(self.label_edit_group, 1, 1) self.property_view_layout_group.addWidget(self.guidance_label_group, 2, 0) self.property_view_layout_group.addWidget(self.guidance_edit_group, 2, 1) spacer = QSpacerItem(40, 200, QSizePolicy.Expanding, QSizePolicy.Minimum) self.property_view_layout_group.addItem(spacer, 3, 0) self.property_view_layout_group.addItem(spacer, 4, 0) self.property_view_layout_group.addItem(spacer, 5, 0) self.property_view_layout_group.addItem(spacer, 6, 0) self.property_view_layout_group.addItem(spacer, 7, 0) self.property_view_layout_group.addItem(spacer, 8, 0) # create pv widget self.property_widget_pv = QWidget() self.property_widget_pv.setWindowTitle("pv") self.property_view_layout_pv = QGridLayout() # add label self.label_edit_pv = QLineEdit() self.label_label_pv = QLabel("NAME") # add guidance self.guidance_edit_pv = QLineEdit() self.guidance_label_pv = QLabel("GUIDANCE") self.property_view_layout_pv.addWidget(self.label_label_pv, 1, 0) self.property_view_layout_pv.addWidget(self.label_edit_pv, 1, 1, 1, 3) self.property_view_layout_pv.addWidget(self.guidance_label_pv, 2, 0) self.property_view_layout_pv.addWidget(self.guidance_edit_pv, 2, 1, 1, 3) # add description self.description_edit = QLineEdit() self.description_label = QLabel("DESCRIPTION") self.property_view_layout_pv.addWidget(self.description_label, 3, 0) self.property_view_layout_pv.addWidget(self.description_edit, 3, 1, 1, 3) # add delay self.delay_edit = QLineEdit() self.delay_label = QLabel("DELAY") self.property_view_layout_pv.addWidget(self.delay_label, 4, 0) self.property_view_layout_pv.addWidget(self.delay_edit, 4, 1, 1, 3) self.delay_edit.setValidator(QIntValidator()) # add count self.count_edit = QLineEdit() self.count_label = QLabel("COUNT") self.property_view_layout_pv.addWidget(self.count_label, 5, 0) self.property_view_layout_pv.addWidget(self.count_edit, 5, 1, 1, 3) self.count_edit.setValidator(QIntValidator()) # add filter/force pv self.filter_edit = QLineEdit() self.filter_label = QLabel("ENABLING FILTER") self.property_view_layout_pv.addWidget(self.filter_label, 6, 0) self.property_view_layout_pv.addWidget(self.filter_edit, 6, 1, 1, 3) # enabled, latching, annunciating self.enabled_check = QCheckBox("ENABLED") self.annunciating_check = QCheckBox("ANNUNCIATING") self.latching_check = QCheckBox("LATCHING") self.property_view_layout_pv.addWidget(self.enabled_check, 7, 0) self.property_view_layout_pv.addWidget(self.annunciating_check, 7, 1) self.property_view_layout_pv.addWidget(self.latching_check, 7, 2) self.property_view_layout_pv.addItem(spacer, 8, 0) # create save button self.button_box = QDialogButtonBox(self) self.button_box.setOrientation(Qt.Horizontal) self.button_box.addButton("Save Properties", QDialogButtonBox.AcceptRole) self.property_layout.addWidget(self.button_box) # self.property_layout.addLayout(self.property_view_layout) self.property_widget_pv.setLayout(self.property_view_layout_pv) self.property_widget_group.setLayout(self.property_view_layout_group) self.property_data_layout.addWidget(self.property_widget_config) self.property_data_layout.addWidget(self.property_widget_pv) self.property_data_layout.addWidget(self.property_widget_group) self.main_layout.addLayout(self.property_layout, 0, 1) self.setWindowTitle("Alarm Tree Editor") self.tree_view.expandAll() def minimumSizeHint(self): # This is the default recommended size # for this screen return QSize(400, 200) def insertChild(self): index = self.tree_view.selectionModel().currentIndex() model = self.tree_view.model() if model.columnCount(index) == 0: if not model.insertColumn(0, index): return if not model.insertRow(0, index): return for column in range(model.columnCount(index)): child = model.index(0, column, index) model.set_data(child, label="NEW_ITEM", role=Qt.EditRole) def removeItem(self): index = self.tree_view.selectionModel().currentIndex() self.tree_view.model().removeRow(index.row(), index.parent()) @Slot() def save_property_changes(self): index = self.tree_view.selectionModel().currentIndex() item = self.tree_view.model().getItem(index) if item.is_group: guidance = self.guidance_edit_group.text() label = self.label_edit_group.text() else: guidance = self.guidance_edit_pv.text() label = self.label_edit_pv.text() self.tree_view.model().set_data( index, label=label, description=self.description_edit.text(), delay=self.delay_edit.text(), count=self.count_edit.text(), enabled=self.enabled_check.isChecked(), annunciating=self.annunciating_check.isChecked(), latching=self.latching_check.isChecked(), alarm_filter=self.filter_edit.text(), guidance=guidance, role=Qt.EditRole, ) @Slot() def handle_selection(self): self.remove_button.setEnabled( self.tree_view.selectionModel().hasSelection()) index = self.tree_view.selectionModel().currentIndex() item = self.tree_view.model().getItem(index) if item.is_group: self.guidance_edit_group.setText(item.guidance) self.label_edit_group.setText(item.label) else: self.guidance_edit_pv.setText(item.guidance) self.label_edit_pv.setText(item.label) if item.is_group: # black for configuration screen if not item.parent: self.property_data_layout.setCurrentWidget( self.property_widget_config) # otherwise show group screen and set all disables else: self.property_data_layout.setCurrentWidget( self.property_widget_group) self.description_edit.setEnabled(False) self.description_edit.setVisible(False) self.description_label.setVisible(False) self.count_edit.setEnabled(False) self.count_edit.setVisible(False) self.count_label.setVisible(False) self.delay_edit.setEnabled(False) self.delay_edit.setVisible(False) self.delay_label.setVisible(False) self.latching_check.setEnabled(False) self.latching_check.setVisible(False) self.annunciating_check.setEnabled(False) self.annunciating_check.setVisible(False) self.filter_edit.setEnabled(False) self.filter_edit.setVisible(False) self.filter_label.setVisible(False) # set pv enabled else: self.property_data_layout.setCurrentWidget(self.property_widget_pv) self.description_edit.setEnabled(True) self.description_edit.setVisible(True) self.description_label.setVisible(True) self.count_edit.setEnabled(True) self.count_edit.setVisible(True) self.count_label.setVisible(True) self.delay_edit.setEnabled(True) self.delay_edit.setVisible(True) self.delay_label.setVisible(True) self.latching_check.setEnabled(True) self.latching_check.setVisible(True) self.annunciating_check.setEnabled(True) self.annunciating_check.setVisible(True) self.filter_edit.setEnabled(True) self.filter_edit.setVisible(True) self.filter_label.setVisible(True) if item.enabled: self.enabled_check.setChecked(True) else: self.enabled_check.setChecked(False) if item.latching: self.latching_check.setChecked(True) else: self.latching_check.setChecked(False) if item.annunciating: self.annunciating_check.setChecked(True) else: self.annunciating_check.setChecked(False) @Slot() def item_change(self): index = self.tree_view.selectionModel().currentIndex() item = self.tree_view.model().getItem(index) if item.is_group: self.guidance_edit_group.setText(item.guidance) self.label_edit_group.setText(item.label) else: self.guidance_edit_pv.setText(item.guidance) self.label_edit_pv.setText(item.label) if item.is_group: if not item.parent(): self.property_data_layout.setCurrentWidget( self.property_widget_config) else: self.property_data_layout.setCurrentWidget( self.property_widget_group) self.description_edit.setEnabled(False) self.description_edit.setVisible(False) self.description_label.setVisible(False) self.count_edit.setEnabled(False) self.count_edit.setVisible(False) self.count_label.setVisible(False) self.delay_edit.setEnabled(False) self.delay_edit.setVisible(False) self.delay_label.setVisible(False) self.latching_check.setEnabled(False) self.latching_check.setVisible(False) self.annunciating_check.setEnabled(False) self.annunciating_check.setVisible(False) self.filter_edit.setEnabled(False) self.filter_edit.setVisible(False) self.filter_label.setVisible(False) else: self.delay_edit.setText(item.delay) self.count_edit.setText(item.count) if item.enabled: self.enabled_check.setChecked(True) else: self.enabled_check.setChecked(False) self.property_data_layout.setCurrentWidget(self.property_widget_pv) self.description_edit.setEnabled(True) self.description_edit.setVisible(True) self.description_label.setVisible(True) self.count_edit.setEnabled(True) self.count_edit.setVisible(True) self.count_label.setVisible(True) self.delay_edit.setEnabled(True) self.delay_edit.setVisible(True) self.delay_label.setVisible(True) self.latching_check.setEnabled(True) self.latching_check.setVisible(True) self.annunciating_check.setEnabled(True) self.annunciating_check.setVisible(True) self.filter_edit.setEnabled(True) self.filter_edit.setVisible(True) self.filter_label.setVisible(True) if item.latching: self.latching_check.setChecked(True) else: self.latching_check.setChecked(False) if item.annunciating: self.annunciating_check.setChecked(True) else: self.annunciating_check.setChecked(False) def ui_filepath(self): # No UI file is being used return None @Slot(bool) def open_file(self, checked): modifiers = QApplication.keyboardModifiers() try: curr_file = self.current_file() folder = os.path.dirname(curr_file) except Exception: folder = os.getcwd() filename = QFileDialog.getOpenFileName( self, "Open File...", folder, "XML (*.xml);; ALH Config (*.alhConfig)") filename = filename[0] if isinstance(filename, (list, tuple)) else filename if filename: filename = str(filename) # if alh file selected, open conversion prompt if filename[-9:] == "alhConfig": self.legacy_window = LegacyWindow(filename) self.legacy_window.exec_() if self.legacy_window.converted_filename: self.import_configuration( self.legacy_window.converted_filename) else: self.import_configuration(filename) def import_configuration(self, filename): nodes = self.config_tool.parse_config(filename) self.tree_view.model().import_hierarchy(nodes) self.tree_label.setText(self.tree_view.model()._nodes[0].label) @Slot() def save_configuration(self): modifiers = QApplication.keyboardModifiers() try: curr_file = self.current_file() folder = os.path.dirname(curr_file) except Exception: folder = os.getcwd() filename = QFileDialog.getSaveFileName(self, "Save File...", folder, "Configration files (*.xml)") filename = filename[0] if isinstance(filename, (list, tuple)) else filename self.config_tool.save_configuration(self.tree_view.model()._root_item, filename) def _update_config_name(self): name = self.tree_label.text() self.tree_view.model()._nodes[0].label = name def _import_legacy_file(self): convert_alh_to_phoebus()
class AlgorithmChoose(QWidget): finished = Signal() started = Signal() result = Signal(SegmentationResult) value_changed = Signal() progress_signal = Signal(str, int) algorithm_changed = Signal(str) def __init__( self, settings: BaseSettings, algorithms: typing.Dict[str, typing.Type[SegmentationAlgorithm]], parent=None ): super().__init__(parent) self.settings = settings self.algorithms = algorithms settings.algorithm_changed.connect(self.updated_algorithm) self.stack_layout = QStackedLayout() self.algorithm_choose = QComboBox() self.algorithm_dict: typing.Dict[str, BaseAlgorithmSettingsWidget] = {} self.algorithm_choose.currentTextChanged.connect(self.change_algorithm) self.add_widgets_to_algorithm() self.settings.image_changed.connect(self.image_changed) # self.setMinimumWidth(370) self.setContentsMargins(0, 0, 0, 0) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.algorithm_choose) layout.addLayout(self.stack_layout) self.setLayout(layout) def add_widgets_to_algorithm(self): self.algorithm_choose.blockSignals(True) self.algorithm_choose.clear() for name, val in self.algorithms.items(): self.algorithm_choose.addItem(name) widget = InteractiveAlgorithmSettingsWidget(self.settings, name, val, []) self.algorithm_dict[name] = widget widget.algorithm_thread.execution_done.connect(self.result.emit) widget.algorithm_thread.finished.connect(self.finished.emit) widget.algorithm_thread.started.connect(self.started.emit) widget.algorithm_thread.progress_signal.connect(self.progress_signal.emit) widget.values_changed.connect(self.value_changed.emit) self.stack_layout.addWidget(widget) name = self.settings.get("current_algorithm", "") self.algorithm_choose.blockSignals(False) if name: self.algorithm_choose.setCurrentText(name) def reload(self, algorithms=None): if algorithms is not None: self.algorithms = algorithms for _ in range(self.stack_layout.count()): widget: InteractiveAlgorithmSettingsWidget = self.stack_layout.takeAt(0).widget() widget.algorithm_thread.execution_done.disconnect() widget.algorithm_thread.finished.disconnect() widget.algorithm_thread.started.disconnect() widget.algorithm_thread.progress_signal.disconnect() widget.values_changed.disconnect() self.algorithm_dict = {} self.add_widgets_to_algorithm() def updated_algorithm(self): self.change_algorithm( self.settings.last_executed_algorithm, self.settings.get(f"algorithms.{self.settings.last_executed_algorithm}"), ) def recursive_get_values(self): result = {} for key, widget in self.algorithm_dict.items(): result[key] = widget.recursive_get_values() self.settings.set("algorithm_widget_state", update(self.settings.get("algorithm_widget_state", dict), result)) return result def change_algorithm(self, name, values: dict = None): self.settings.set("current_algorithm", name) widget = self.stack_layout.currentWidget() self.blockSignals(True) if name != widget.name: widget = self.algorithm_dict[name] self.stack_layout.setCurrentWidget(widget) widget.image_changed(self.settings.image) if hasattr(widget, "set_mask") and hasattr(self.settings, "mask"): widget.set_mask(self.settings.mask) elif values is None: self.blockSignals(False) return if values is not None: widget.set_values(values) self.algorithm_choose.setCurrentText(name) self.blockSignals(False) self.algorithm_changed.emit(name) def image_changed(self): current_widget: InteractiveAlgorithmSettingsWidget = self.stack_layout.currentWidget() if hasattr(self.settings, "mask") and hasattr(current_widget, "change_mask"): current_widget.change_mask() current_widget.image_changed(self.settings.image) def mask_changed(self): current_widget: InteractiveAlgorithmSettingsWidget = self.stack_layout.currentWidget() if hasattr(self.settings, "mask") and hasattr(current_widget, "change_mask"): current_widget.change_mask() def current_widget(self) -> InteractiveAlgorithmSettingsWidget: return self.stack_layout.currentWidget() def current_parameters(self) -> SegmentationProfile: widget = self.current_widget() return SegmentationProfile("", widget.name, widget.get_values()) def get_info_text(self): return self.current_widget().algorithm_thread.get_info_text()
class TabContainer(AbstractContainer): dockMove = Signal(object, object, object) class Point(object): def __init__(self, x, y): self.__x = x self.__y = y def x(self): return self.__x def y(self): return self.__y def __init__(self, bench, parent_container): """ Container which layouts its child items on stacked layout and provides a tab bar. :param bench: :param parent_container: """ super(TabContainer, self).__init__(bench, parent_container) self.setAcceptDrops(True) self.refDropRegions = None self.absDropRegions = None self.overlay = DropOverlay(self) self.layout = QVBoxLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(0) if CONFIG.debug_layout: self.layout.setContentsMargins(2, 16, 2, 2) self.setLayout(self.layout) self._tabbar = TabHeader() self.layout.addWidget(self._tabbar) self._dockstack = QStackedLayout() self._dockstack.setContentsMargins(0, 0, 0, 0) self._dockstack.setSpacing(0) self.layout.addLayout(self._dockstack) def activateTab(self, uid): _log.debug("Activating tab for dock: {}".format(uid)) for d in self.flatDockList: if d.uid == uid: self._dockstack.setCurrentWidget(d) d.tab.setActive(True) else: d.tab.setActive(False) def closeChild(self, uid): _log.debug("Closing dock: {}".format(uid)) was_active = False for d in self.flatDockList: if d.uid == uid: _log.debug("Removing dock: {}".format(d)) was_active = d.tab.active d.tab.setParent(None) d.tab._dock = None d.parentContainer = None d.close() d.deleteLater() d.setParent(None) self.contentModified.emit() if self._dockstack.count() == 0: # Container is empty, close and propagate self.closing.emit(self._uid) elif was_active: dock = self.flatDockList[0] self.activateTab(dock.uid) @property def flatDockList(self): return [ self._dockstack.itemAt(k).widget() for k in range(self._dockstack.count()) ] @property def docks(self): return self.flatDockList def addItem(self, index, item): # _config.debug check if not isinstance(item, Dock): raise BenchException("Misuse") item.parentContainer = self item.closing.connect(self.closeChild) item.activated.connect(self.activateTab) self._tabbar.addTab(index, item) self._dockstack.insertWidget(index, item) self.activateTab(item.uid) item.setVisible(True) item.tab.setVisible(True) @classmethod def __checkEventMimeTypeData(cls, event): """ Checks the drag events MIME type, and that at least two dockbench items on the area. :param event: drag event / drop event :return: true when the MIME type can be handled """ if not event.mimeData().hasFormat(MIME_TYPE): event.ignore() return False event.accept() return True def dragEnterEvent(self, event): _log.debug("TabContainer: Drag enter event") if not self.__checkEventMimeTypeData(event): return data = event.mimeData().data(MIME_TYPE).data() dock_uid = pickle.loads(data) _log.debug("ETID: {}".format(dock_uid)) for d in self.flatDockList: if d.uid == dock_uid and len(self.flatDockList) == 1: _log.debug("Tab in container") event.ignore() return if self.rect().width() < 200 and self.rect().height() < 200: _log.debug("To less widget left...") event.ignore() return if self.overlay.isHidden(): _log.debug("Drop overlay") self.overlay.raise_() self.overlay.show() w = self._dockstack.currentWidget() pos = w.mapTo(self, QPoint(0, 0)) rect = QRect(pos.x(), pos.y(), w.width(), w.height()) self.overlay.setGeometry(rect) xc = pos.x() + w.width() / 2.0 yc = pos.y() + w.height() / 2.0 self.refDropRegions = { TabContainer.Point(xc - 34, yc): Placement.LEFT, TabContainer.Point(xc + 34, yc): Placement.RIGHT, TabContainer.Point(xc, yc - 34): Placement.TOP, TabContainer.Point(xc, yc + 34): Placement.BOTTOM, TabContainer.Point(xc, yc): Placement.TAB, } self.absDropRegions = { TabContainer.Point(xc - 68, yc): Placement.LEFT, TabContainer.Point(xc + 68, yc): Placement.RIGHT, TabContainer.Point(xc, yc - 68): Placement.TOP, TabContainer.Point(xc, yc + 68): Placement.BOTTOM, } def dragMoveEvent(self, event): if not self.__checkEventMimeTypeData(event): return pos = event.pos() x = pos.x() y = pos.y() for region in self.refDropRegions: if abs(region.x() - x) <= 12 and abs(region.y() - y) <= 12: _log.debug("Drop ref over: {}".format( self.refDropRegions[region])) event.accept() self.overlay.setActiveDropRegion(self.refDropRegions[region]) return for region in self.absDropRegions: if abs(region.x() - x) <= 12 and abs(region.y() - y) <= 12: _log.debug("Drop abs over: {}".format( self.absDropRegions[region])) event.accept() self.overlay.setActiveDropRegion(self.absDropRegions[region], True) return self.overlay.setActiveDropRegion(None) event.ignore() def dragLeaveEvent(self, event): _log.debug("TabContainer: Drag leave event") if not self.overlay.isHidden(): self.overlay.hide() self.refDropRegions = None self.absDropRegions = None def dropEvent(self, event): _log.debug("TabContainer: Drop event") if not self.__checkEventMimeTypeData(event): event.ignore() return data = event.mimeData().data(MIME_TYPE).data() dock_uid = pickle.loads(data) _log.debug("ETID: {}".format(dock_uid)) pos = event.pos() x = pos.x() y = pos.y() for region in self.refDropRegions: if abs(region.x() - x) <= 12 and abs(region.y() - y) <= 12: _log.debug("Drop ref over: {}".format( self.refDropRegions[region])) ref = self._dockstack.currentWidget() # self.dockMove.emit(dock_uid, self.refDropRegions[region], ref.uid) self._bench.dockMove(dock_uid, self.refDropRegions[region], ref.uid) break if self.absDropRegions is not None: for region in self.absDropRegions: if abs(region.x() - x) <= 12 and abs(region.y() - y) <= 12: _log.debug("Drop abs over: {}".format( self.absDropRegions[region])) # self.dockMove.emit(dock_uid, self.absDropRegions[region], None) self._bench.dockMove(dock_uid, self.absDropRegions[region], None) break if not self.overlay.isHidden(): self.overlay.hide() self.refDropRegions = None self.absDropRegions = None def saveLayout(self): layout = super(TabContainer, self).saveLayout() children = [] for dock in self.flatDockList: children.append(dock.saveLayout()) layout["children"] = children return layout def loadLayout(self, layout): for k, child in enumerate(layout["children"]): module_str = child["module"] class_str = child["class"] # Bootstrap part II: Make the child containers mod = __import__(module_str, fromlist=[class_str]) klass = getattr(mod, class_str) child_obj = klass() self.addItem(k, child_obj) child_obj.loadLayout(child)