class SectionRegistry(object): def __init__(self): self._registry = OrderedDict() def register_section(self, section): self._registry[section.SECTION_NAME] = section def unregister_section(self, name): del self._registry[name] def get_section(self, name): return self._registry[name] def get_section_names(self): return self._registry.keys() def get_path_settings(self): result = [] for section_name, section in self._registry.iteritems(): for trait_name in section.get_trait_names(): trait = section.get_trait(trait_name) if (isinstance(trait, StringTrait) and trait.widget_info in [StringTrait.STRING_FILE, StringTrait.STRING_PATH]): result.append((section_name, trait_name, trait)) return result
class TraitGroup(object): def __init__(self, name): self.name = name self._registry = OrderedDict() def register_trait(self, name, trait): self._registry[name] = trait def get_trait(self, name): return self._registry[name] def get_trait_names(self): return self._registry.keys()
class _Section(object): SECTION_NAME = None OPTIONS = None def __init__(self): self._registry = OrderedDict() self._traitname_grpname = OrderedDict() for grp_name, grp_items in self.OPTIONS: grp = TraitGroup(grp_name) self._registry[grp_name] = grp for trait_name, trait in grp_items: trait_name = trait_name.lower() grp.register_trait(trait_name, trait) self._traitname_grpname[trait_name] = grp_name def get_group(self, name): return self._registry[name] def get_group_names(self): return self._registry.keys() def get_trait(self, trait_name): grp_name = self._traitname_grpname[trait_name] grp = self._registry[grp_name] return grp.get_trait(trait_name) def get_trait_names(self): names = [] for grp in self._registry.values(): names += grp.get_trait_names() return set(names) def get_trait_names_for_group(self, name): grp = self.get_group(name) return grp.get_trait_names()
class PluginManager(object): PREFIX = 'plugin' LABEL = '' def __init__(self, display_name, name, section): super(PluginManager, self).__init__() self.display_name = display_name self.name = name self.section = section self._plugins = OrderedDict() self._instances = OrderedDict() self._observer = [] def init_from_settings(self, settings): plugin_params = {} plugin_cls_names = {} for option_name in settings.options(self.section): items = option_name.split('__') if len(items ) > 4 and items[0] == self.PREFIX and items[1] == self.name: plugin_cls_name = items[2] plugin_name = items[3] params = items[4] if not plugin_name in plugin_cls_names: plugin_cls_names[plugin_name] = plugin_cls_name else: assert plugin_cls_names[plugin_name] == plugin_cls_name plugin_params.setdefault(plugin_name, []).append( (option_name, params)) for plugin_name in plugin_params: plugin_cls_name = plugin_cls_names[plugin_name] plugin_cls = self._plugins[plugin_cls_name] param_manager = \ ParamManager.from_settings(plugin_cls, plugin_name, settings, self, plugin_params[plugin_name]) instance = plugin_cls(plugin_name, param_manager) self._instances[plugin_name] = instance self.notify_instance_modified(plugin_name) for observer in self._observer: observer.init() def clear(self): for plugin_name, instance in self._instances.iteritems(): instance.close() self.notify_instance_modified(plugin_name, True) self._instances.clear() def add_instance(self, plugin_cls_name, settings): if not plugin_cls_name in self._plugins: raise ValueError("Plugin '%s' not registered for '%s'." % (plugin_cls_name, self.name)) plugin_cls = self._plugins[plugin_cls_name] plugin_name = self._get_plugin_name(plugin_cls) param_manager = ParamManager(plugin_cls, plugin_name, settings, self) instance = plugin_cls(plugin_name, param_manager) self._instances[plugin_name] = instance self.notify_instance_modified(plugin_name) return plugin_name def remove_instance(self, plugin_name, settings): if not plugin_name in self._instances: raise ValueError("Plugin instance '%s' not found for '%s'." % (plugin_name, self.name)) plugin = self._instances[plugin_name] plugin.close() del self._instances[plugin_name] self.notify_instance_modified(plugin_name, True) def notify_instance_modified(self, plugin_name, removed=False): for observer in self._observer: observer.notify(plugin_name, removed) def _get_plugin_name(self, plugin_cls): """ generate new plugin name which is not used yet. starting at the plugin class NAME and appending numbers from 2 to n, like 'primary', 'primary2', 'primary3' """ cnt = 2 result = plugin_cls.NAME while result in self._instances: result = plugin_cls.NAME + str(cnt) cnt += 1 return result def get_trait_name_template(self, plugin_cls_name, plugin_name): return '__'.join( [self.PREFIX, self.name, plugin_cls_name, plugin_name, '%s']) def register_plugin(self, plugin_cls): self._plugins[plugin_cls.NAME] = plugin_cls def register_observer(self, observer): self._observer.append(observer) def unregister_observer(self, observer): self._observer.remove(observer) def add_referee_to_instance(self, plugin_name, referee): instance = self.get_plugin_instance(plugin_name) instance.add_referee(referee) def remove_referee_from_instance(self, plugin_name, referee): instance = self.get_plugin_instance(plugin_name) instance.remove_referee(referee) def handle_referee(self, plugin_name_new, plugin_name_old, referee): # remove old and add new referee to instance #print self.name, plugin_name_new, plugin_name_old, referee if plugin_name_old in self._instances: self.remove_referee_from_instance(plugin_name_old, referee) if plugin_name_new in self._instances: self.add_referee_to_instance(plugin_name_new, referee) def get_referees_for_instance(self, plugin_name): instance = self.get_plugin_instance(plugin_name) return instance.referees def get_plugin_cls_names(self): return self._plugins.keys() def get_plugin_labels(self): return [(name, cls.LABEL) for name, cls in self._plugins.iteritems()] def get_plugin_names(self): return sorted(self._instances.keys()) def get_plugin_cls(self, name): return self._plugins[name] def get_plugin_instance(self, name): return self._instances[name] def number_loaded_plugins(self): """Return the number of plugins that are invoked if run is executed.""" return len(self._instances) # FIXME **option is dangerous, what if one calls run(foo=bar) @stopwatch(level=logging.INFO) def run(self, *args, **options): results = OrderedDict() for instance in self._instances.itervalues(): inst_args = list(args) if not instance.REQUIRES is None: if not 'requirements' in options: raise ValueError(( "PluginManager(%s).run needs 'requirements' options, " "because Plugin instances '%s' defines requirements." % (self.name, instance.name))) requirements = options['requirements'] for idx in range(len(instance.REQUIRES)): value = instance.get_requirement_info(idx)[1] data = requirements[idx].get_requirement(value) inst_args.append(data) results[instance.name] = instance.run(*inst_args) return results
class PluginBay(QFrame): def __init__(self, parent, plugin_manager, settings): super(QFrame, self).__init__(parent) self.plugin_manager = plugin_manager self.plugin_manager.register_observer(self) self.settings = settings self._plugins = OrderedDict() self.setStyleSheet( "PluginItem { border: 1px solid black; background: white; }") layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) frame1 = QFrame(self) self._frame2 = QFrame(self) layout.addWidget(frame1) layout.addSpacing(10) layout.addWidget(self._frame2) label = QLabel('%s plugins' % plugin_manager.display_name, frame1) label.setStyleSheet("font-weight: bold;") btn = QPushButton('Add', frame1) btn.clicked.connect(self._on_add_plugin) self._cb = QComboBox(frame1) self._set_plugin_labels() layout = QHBoxLayout(frame1) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(label) layout.addWidget(self._cb, 1) layout.addWidget(btn) layout = QVBoxLayout(self._frame2) layout.setContentsMargins(0, 0, 0, 0) def init(self): self.reset() for plugin_name in self.plugin_manager.get_plugin_names(): self.add_plugin(plugin_name) def notify(self, plugin_name, removed): pass def _set_plugin_labels(self): self._cb.clear() for name, label in self.plugin_manager.get_plugin_labels(): self._cb.addItem(label, name) def reset(self): self._set_plugin_labels() for plugin_name in self._plugins.keys()[:]: self.remove_plugin(plugin_name) def add_plugin(self, plugin_name): plugin = self.plugin_manager.get_plugin_instance(plugin_name) item = PluginItem(self._frame2, plugin, self.settings) item.remove_item.connect( functools.partial(self._on_remove_plugin, plugin_name)) layout = self._frame2.layout() layout.insertWidget(0, item) self._plugins[plugin_name] = item def remove_plugin(self, plugin_name): layout = self._frame2.layout() item = self._plugins[plugin_name] item.close() layout.removeWidget(item) del self._plugins[plugin_name] def _on_add_plugin(self): name_cls = self._cb.itemData(self._cb.currentIndex()) plugin_name = self.plugin_manager.add_instance(name_cls, self.settings) self.add_plugin(plugin_name) def _on_remove_plugin(self, plugin_name): instance = self.plugin_manager.get_plugin_instance(plugin_name) result = False n = len(instance.referees) if n == 0: result = question(None, 'Removing the plugin "%s"' % plugin_name, "Are you sure to remove this plugin?") elif n > 0: detail = '\n'.join(['%s (%s)' % x[:2] for x in instance.referees]) result = question( None, 'Removing the plugin "%s"' % plugin_name, '%d other plugin%s require%s this plugin.\n\nAre you sure to remove this plugin?' % (n, 's' if n > 1 else '', 's' if n == 1 else ''), detail=detail, icon=QMessageBox.Warning) if result: self.remove_plugin(plugin_name) self.plugin_manager.remove_instance(plugin_name, self.settings)
class TabControl(QFrame): """General tab control: list of buttons at the top and stacked widget below""" current_changed = pyqtSignal(int) def __init__(self, parent, hide_one=True): QFrame.__init__(self, parent) self.setStyleSheet(TAB_STYLE) self._hide_one = hide_one self._tabs = OrderedDict() layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self._btn_frame = QFrame(self) self._btn_grp = QButtonGroup(self._btn_frame) self._btn_grp.setExclusive(True) self._btn_layout = QHBoxLayout(self._btn_frame) self._btn_layout.insertStretch(0, 1) self._btn_layout.insertStretch(1, 1) self._stacked_frame = QStackedWidget(self) self._stacked_frame.setObjectName('stacked') layout.addWidget(self._btn_frame) layout.addWidget(self._stacked_frame) self._btn_frame.hide() self._current_name = None def add_tab(self, name, frame): scroll_area = QScrollArea(self) scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll_area.setWidgetResizable(True) scroll_area.setFrameShape(QFrame.NoFrame) scroll_area.setWidget(frame) btn = QPushButton(name, self._btn_frame) btn.setObjectName('tab') btn.setFlat(True) btn.setCheckable(True) btn.toggled.connect(functools.partial(self._on_btn_toggled, name)) self._btn_grp.addButton(btn) self._tabs[name] = (scroll_area, frame, btn) self._stacked_frame.addWidget(scroll_area) self._btn_layout.insertWidget(len(self._tabs), btn) if not self._hide_one or len(self._tabs) > 1: self._btn_frame.show() self._current_name = name def remove_tab(self, name): scroll_area = self._tabs[name][0] btn = self._tabs[name][1] self._btn_layout.removeWidget(btn) self._stacked_frame.removeWidget(scroll_area) if (self._hide_one and len(self._tabs) <= 1) or (not self._hide_one and len(self._tabs) == 0): self._btn_frame.hide() def set_active(self, name, toggle=True): scroll_area = self._tabs[name][0] if toggle: btn = self._tabs[name][2] btn.setChecked(True) self._stacked_frame.setCurrentWidget(scroll_area) self._current_name = name self.current_changed.emit(self._tabs.index(name)) def set_active_index(self, index): name = self._tabs.keys()[index] self.set_active(name) def _on_btn_toggled(self, name): self.set_active(name, toggle=False) def enable_non_active(self, state=True): for name in self._tabs: frame, btn = self._tabs[name][1:] if frame != self._stacked_frame.currentWidget(): btn.setEnabled(state) def get_frame(self, name): return self._tabs[name][1] @property def current_name(self): return self._current_name @property def current_index(self): return self._tabs.index(self._current_name)
class CellAnalyzer(PropertyManager): PROPERTIES = \ dict(P = StringProperty(True, doc=''), bCreateImages = BooleanProperty(True, doc="Create output images"), iBinningFactor = IntProperty(None, is_mandatory=True, doc=''), detect_objects = BooleanProperty(True), time_holder = InstanceProperty(None, TimeHolder, doc="Instance of TimeHolder.", is_mandatory=True), ) __attributes__ = [Attribute('_channel_registry'), Attribute('_iT'), Attribute('_oLogger'), ] def __init__(self, **dctOptions): super(CellAnalyzer, self).__init__(**dctOptions) self._oLogger = logging.getLogger(self.__class__.__name__) def initTimepoint(self, iT): self._channel_registry = OrderedDict() self._iT = iT self.time_holder.initTimePoint(iT) def register_channel(self, channel): self._channel_registry[channel.NAME] = channel def get_channel_names(self): return self._channel_registry.keys() def get_channel(self, name): return self._channel_registry[name] def process(self, apply=True, extract_features=True): # sort by Channel `RANK` channels = sorted(self._channel_registry.values()) primary_channel = None for channel in channels: self.time_holder.prepare_raw_image(channel) if self.detect_objects: self.time_holder.apply_segmentation(channel, primary_channel) if extract_features: self.time_holder.apply_features(channel) if primary_channel is None: assert channel.RANK == 1 primary_channel = channel if apply: for channel in channels: self.time_holder.apply_channel(channel) def purge(self, features=None): for oChannel in self._channel_registry.values(): if not features is None and oChannel.strChannelId in features: channelFeatures = features[oChannel.strChannelId] else: channelFeatures = None oChannel.purge(features=channelFeatures) def exportLabelImages(self, pathOut, compression='LZW'): for name, channel in self._channel_registry.iteritems(): channel_id = channel.strChannelId for strRegion, oContainer in channel.dctContainers.iteritems(): strPathOutImage = os.path.join(pathOut, channel_id, strRegion) safe_mkdirs(strPathOutImage) oContainer.exportLabelImage(os.path.join(strPathOutImage, 'P%s_T%05d.tif' % (self.P, self._iT)), compression) def getImageSize(self, name): oChannel = self._channel_registry[name] w = oChannel.meta_image.width h = oChannel.meta_image.height return (w,h) def render(self, strPathOut, dctRenderInfo=None, strFileSuffix='.jpg', strCompression='98', writeToDisc=True, images=None): lstImages = [] if not images is None: lstImages += images if dctRenderInfo is None: for name, oChannel in self._channel_registry.iteritems(): for strRegion, oContainer in oChannel.dctContainers.iteritems(): strHexColor, fAlpha = oChannel.dctAreaRendering[strRegion] imgRaw = oChannel.meta_image.image imgCon = ccore.Image(imgRaw.width, imgRaw.height) ccore.drawContour(oContainer.getBinary(), imgCon, 255, False) lstImages.append((imgRaw, strHexColor, 1.0)) lstImages.append((imgCon, strHexColor, fAlpha)) else: for channel_name, dctChannelInfo in dctRenderInfo.iteritems(): if channel_name in self._channel_registry: oChannel = self._channel_registry[channel_name] if 'raw' in dctChannelInfo: strHexColor, fAlpha = dctChannelInfo['raw'] lstImages.append((oChannel.meta_image.image, strHexColor, fAlpha)) if 'contours' in dctChannelInfo: # transform the old dict-style to the new tuple-style, # which allows multiple definitions for one region if type(dctChannelInfo['contours']) == types.DictType: lstContourInfos = [(k,)+v for k,v in dctChannelInfo['contours'].iteritems()] else: lstContourInfos = dctChannelInfo['contours'] for tplData in lstContourInfos: strRegion, strNameOrColor, fAlpha, bShowLabels = tplData[:4] # draw contours only if region is present if oChannel.has_region(strRegion): if len(tplData) > 4: bThickContours = tplData[4] else: bThickContours = False if strNameOrColor == 'class_label': oContainer = oChannel.dctContainers[strRegion] oRegion = oChannel.get_region(strRegion) dctLabels = {} dctColors = {} for iObjId, oObj in oRegion.iteritems(): iLabel = oObj.iLabel if not iLabel is None: if not iLabel in dctLabels: dctLabels[iLabel] = [] dctLabels[iLabel].append(iObjId) dctColors[iLabel] = oObj.strHexColor #print dctLabels imgRaw = oChannel.meta_image.image imgCon2 = ccore.Image(imgRaw.width, imgRaw.height) for iLabel, lstObjIds in dctLabels.iteritems(): imgCon = ccore.Image(imgRaw.width, imgRaw.height) oContainer.drawContoursByIds(lstObjIds, 255, imgCon, bThickContours, False) lstImages.append((imgCon, dctColors[iLabel], fAlpha)) if type(bShowLabels) == types.BooleanType and bShowLabels: # oContainer.drawTextsByIds(lstObjIds, lstObjIds, imgCon2) #else: oContainer.drawTextsByIds(lstObjIds, [str(iLabel)]*len(lstObjIds), imgCon2) lstImages.append((imgCon2, '#FFFFFF', 1.0)) else: oContainer = oChannel.dctContainers[strRegion] oRegion = oChannel.get_region(strRegion) lstObjIds = oRegion.keys() imgRaw = oChannel.meta_image.image imgCon = ccore.Image(imgRaw.width, imgRaw.height) if not strNameOrColor is None: oContainer.drawContoursByIds(lstObjIds, 255, imgCon, bThickContours, False) else: strNameOrColor = '#FFFFFF' lstImages.append((imgCon, strNameOrColor, fAlpha)) if bShowLabels: imgCon2 = ccore.Image(imgRaw.width, imgRaw.height) oContainer.drawLabelsByIds(lstObjIds, imgCon2) lstImages.append((imgCon2, '#FFFFFF', 1.0)) if len(lstImages) > 0: imgRgb = ccore.makeRGBImage([x[0].getView() for x in lstImages], [ccore.RGBValue(*hexToRgb(x[1])) for x in lstImages], [x[2] for x in lstImages]) if writeToDisc: strFilePath = os.path.join(strPathOut, "P%s_T%05d%s" % (self.P, self._iT, strFileSuffix)) safe_mkdirs(strPathOut) ccore.writeImage(imgRgb, strFilePath, strCompression) self._oLogger.debug("* rendered image written '%s'" % strFilePath) else: strFilePath = '' return imgRgb, strFilePath def collectObjects(self, plate_id, P, lstReader, oLearner, byTime=True): #channel_name = oLearner.strChannelId strRegionId = oLearner.strRegionId img_rgb = None self._oLogger.debug('* collecting samples...') # bSuccess = True # channels = sorted(self._channel_registry.values()) # primary_cChannel = None # for channel2 in lstChannels: # # self.time_holder.prepare_raw_image(channel) # self.time_holder.apply_segmentation(oChannel2, oPrimaryChannel) # # if oPrimaryChannel is None: # assert oChannel2.RANK == 1 # oPrimaryChannel = oChannel2 self.process(apply = False, extract_features = False) # self._channel_registry oChannel = self._channel_registry[oLearner.channel_name] oContainer = oChannel.get_container(strRegionId) objects = oContainer.getObjects() object_lookup = {} for oReader in lstReader: lstCoordinates = None if (byTime and P == oReader.getPosition() and self._iT in oReader): lstCoordinates = oReader[self._iT] elif (not byTime and P in oReader): lstCoordinates = oReader[P] #print "moo", P, oReader.getPosition(), byTime, self._iT in oReader #print lstCoordinates, byTime, self.P, oReader.keys() if not lstCoordinates is None: #print self.iP, self._iT, lstCoordinates for dctData in lstCoordinates: label = dctData['iClassLabel'] if (label in oLearner.dctClassNames and dctData['iPosX'] >= 0 and dctData['iPosX'] < oContainer.width and dctData['iPosY'] >= 0 and dctData['iPosY'] < oContainer.height): center1 = ccore.Diff2D(dctData['iPosX'], dctData['iPosY']) # test for obj_id "under" annotated pixel first obj_id = oContainer.img_labels[center1] # if not background: valid obj_id found if obj_id > 0: dict_append_list(object_lookup, label, obj_id) # otherwise try to find nearest object in a search # radius of 30 pixel (compatibility with CellCounter) else: dists = [] for obj_id, obj in objects.iteritems(): diff = obj.oCenterAbs - center1 dist_sq = diff.squaredMagnitude() # limit to 30 pixel radius if dist_sq < 900: dists.append((obj_id, dist_sq)) if len(dists) > 0: dists.sort(lambda a,b: cmp(a[1], b[1])) obj_id = dists[0][0] dict_append_list(object_lookup, label, obj_id) object_ids = set(flatten(object_lookup.values())) objects_del = set(objects.keys()) - object_ids for obj_id in objects_del: oContainer.delObject(obj_id) self.time_holder.apply_features(oChannel) region = oChannel.get_region(strRegionId) learner_objects = [] for label, object_ids in object_lookup.iteritems(): class_name = oLearner.dctClassNames[label] hex_color = oLearner.dctHexColors[class_name] rgb_value = ccore.RGBValue(*hexToRgb(hex_color)) for obj_id in object_ids: obj = region[obj_id] obj.iLabel = label obj.strClassName = class_name obj.strHexColor = hex_color if (obj.oRoi.upperLeft[0] >= 0 and obj.oRoi.upperLeft[1] >= 0 and obj.oRoi.lowerRight[0] < oContainer.width and obj.oRoi.lowerRight[1] < oContainer.height): iCenterX, iCenterY = obj.oCenterAbs strPathOutLabel = os.path.join(oLearner.dctEnvPaths['samples'], oLearner.dctClassNames[label]) safe_mkdirs(strPathOutLabel) strFilenameBase = 'PL%s___P%s___T%05d___X%04d___Y%04d' % (plate_id, self.P, self._iT, iCenterX, iCenterY) obj.sample_id = strFilenameBase learner_objects.append(obj) strFilenameImg = os.path.join(strPathOutLabel, '%s___img.png' % strFilenameBase) strFilenameMsk = os.path.join(strPathOutLabel, '%s___msk.png' % strFilenameBase) # FIXME: export Objects is segfaulting for objects # where its bounding box is touching the border # i.e. one corner point equals zero! oContainer.exportObject(obj_id, strFilenameImg, strFilenameMsk) oContainer.markObjects([obj_id], rgb_value, False, True) #print obj_id, obj.oCenterAbs, iCenterX, iCenterY print '*** CSdebug: drawFilledCircle', iCenterX, iCenterY ccore.drawFilledCircle(ccore.Diff2D(iCenterX, iCenterY), 3, oContainer.img_rgb, rgb_value) if len(learner_objects) > 0: oLearner.applyObjects(learner_objects) # we don't want to apply None for feature names oLearner.setFeatureNames(oChannel.lstFeatureNames) strPathOut = os.path.join(oLearner.dctEnvPaths['controls']) safe_mkdirs(strPathOut) oContainer.exportRGB(os.path.join(strPathOut, "P%s_T%05d_C%s_R%s.jpg" %\ (self.P, self._iT, oLearner.strChannelId, oLearner.strRegionId)), '90') img_rgb = oContainer.img_rgb return img_rgb def classifyObjects(self, oPredictor): channel_name = oPredictor.strChannelId strRegionId = oPredictor.strRegionId oChannel = self._channel_registry[channel_name] oRegion = oChannel.get_region(strRegionId) for iObjId, oObj in oRegion.iteritems(): iLabel, dctProb = oPredictor.predict(oObj.aFeatures.copy(), oRegion.getFeatureNames()) oObj.iLabel = iLabel oObj.dctProb = dctProb oObj.strClassName = oPredictor.dctClassNames[iLabel] oObj.strHexColor = oPredictor.dctHexColors[oObj.strClassName]
class PluginBay(QFrame): def __init__(self, parent, plugin_manager, settings): super(QFrame, self).__init__(parent) self.plugin_manager = plugin_manager self.plugin_manager.register_observer(self) self.settings = settings self._plugins = OrderedDict() self.setStyleSheet("PluginItem { border: 1px solid black; background: white; }") layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) frame1 = QFrame(self) self._frame2 = QFrame(self) layout.addWidget(frame1) layout.addSpacing(10) layout.addWidget(self._frame2) label = QLabel('%s plugins' % plugin_manager.display_name, frame1) label.setStyleSheet("font-weight: bold;") btn = QPushButton('Add', frame1) btn.clicked.connect(self._on_add_plugin) self._cb = QComboBox(frame1) self._set_plugin_labels() layout = QHBoxLayout(frame1) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(label) layout.addWidget(self._cb, 1) layout.addWidget(btn) layout = QVBoxLayout(self._frame2) layout.setContentsMargins(0, 0, 0, 0) def init(self): self.reset() for plugin_name in self.plugin_manager.get_plugin_names(): self.add_plugin(plugin_name) def notify(self, plugin_name, removed): pass def _set_plugin_labels(self): self._cb.clear() for name, label in self.plugin_manager.get_plugin_labels(): self._cb.addItem(label, name) def reset(self): self._set_plugin_labels() for plugin_name in self._plugins.keys()[:]: self.remove_plugin(plugin_name) def add_plugin(self, plugin_name): plugin = self.plugin_manager.get_plugin_instance(plugin_name) item = PluginItem(self._frame2, plugin, self.settings) item.remove_item.connect(functools.partial(self._on_remove_plugin, plugin_name)) layout = self._frame2.layout() layout.insertWidget(0, item) self._plugins[plugin_name] = item def remove_plugin(self, plugin_name): layout = self._frame2.layout() item = self._plugins[plugin_name] item.close() layout.removeWidget(item) del self._plugins[plugin_name] def _on_add_plugin(self): name_cls = self._cb.itemData(self._cb.currentIndex()) plugin_name = self.plugin_manager.add_instance(name_cls, self.settings) self.add_plugin(plugin_name) def _on_remove_plugin(self, plugin_name): instance = self.plugin_manager.get_plugin_instance(plugin_name) result = False n = len(instance.referees) if n == 0: result = question(None, 'Removing the plugin "%s"' % plugin_name, "Are you sure to remove this plugin?") elif n > 0: detail = '\n'.join(['%s (%s)' % x[:2] for x in instance.referees]) result = question(None, 'Removing the plugin "%s"' % plugin_name, '%d other plugin%s require%s this plugin.\n\nAre you sure to remove this plugin?' % (n, 's' if n > 1 else '', 's' if n == 1 else ''), detail=detail, icon=QMessageBox.Warning) if result: self.remove_plugin(plugin_name) self.plugin_manager.remove_instance(plugin_name, self.settings)