def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize()
def __scrollAreaKeyReleaseEvent(self, event): modifiers = int(event.modifiers()) self._time.keyReleaseEvent(event) if modifiers is not QtCore.Qt.ControlModifier and \ modifiers is not int(QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier) and \ modifiers is not QtCore.Qt.ShiftModifier: QScrollArea.keyReleaseEvent(self._scrollArea, event)
def __init__(self, parent=None, windowTitle="Color Palette List", **kwargs): super().__init__(parent, windowTitle=windowTitle, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) sa = QScrollArea(horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) sa.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) sa.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.layout().addWidget(sa) space = QWidget(self) space.setLayout(QVBoxLayout()) sa.setWidget(space) sa.setWidgetResizable(True) self.buttons = [] self.setMinimumWidth(400) box = gui.vBox(space, "Information", addSpace=True) gui.widgetLabel( box, '<p align="center">This dialog shows a list of predefined ' 'color palettes <br>from colorbrewer.org that can be used ' 'in Orange.<br/>You can select a palette by clicking on it.</p>') box = gui.vBox(space, "Default Palette", addSpace=True) butt = _ColorButton(DefaultRGBColors, flat=True, toolTip="Default color palette", clicked=self._buttonClicked) box.layout().addWidget(butt) self.buttons.append(butt) for type in [ "Qualitative", "Spectral", "Diverging", "Sequential", "Pastels" ]: colorGroup = colorbrewer.colorSchemes.get(type.lower(), {}) if colorGroup: box = gui.vBox(space, type + " Palettes", addSpace=True) items = sorted(colorGroup.items()) for key, colors in items: butt = _ColorButton(colors, self, toolTip=key, flat=True, clicked=self._buttonClicked) box.layout().addWidget(butt) self.buttons.append(butt) buttons = QDialogButtonBox(QDialogButtonBox.Cancel, rejected=self.reject) self.layout().addWidget(buttons) self.selectedColors = None
def __init__(self, parent=None): super().__init__(parent) self.corpus = None self.initial_ngram_range = None # initial range of input corpus — used for inplace self.preprocessor = preprocess.Preprocessor() # -- INFO -- info_box = gui.widgetBox(self.controlArea, 'Info') info_box.setFixedWidth(self.control_area_width) self.controlArea.layout().addStretch() self.info_label = gui.label(info_box, self, '') self.update_info() # -- PIPELINE -- frame = QFrame() frame.setContentsMargins(0, 0, 0, 0) frame.setFrameStyle(QFrame.Box) frame.setStyleSheet('.QFrame { border: 1px solid #B3B3B3; }') frame_layout = QVBoxLayout() frame_layout.setContentsMargins(0, 0, 0, 0) frame_layout.setSpacing(0) frame.setLayout(frame_layout) self.stages = [] for stage in self.preprocessors: widget = stage(self) self.stages.append(widget) setattr(self, stage.attribute, widget) frame_layout.addWidget(widget) widget.change_signal.connect(self.settings_invalidated) frame_layout.addStretch() self.scroll = QScrollArea() self.scroll.setWidget(frame) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.resize(frame_layout.sizeHint()) self.scroll.setMinimumHeight(500) self.set_minimal_width() self.mainArea.layout().sizeHint() self.mainArea.layout().addWidget(self.scroll) # Buttons area self.report_button.setFixedWidth(self.control_area_width) commit_button = gui.auto_commit(self.buttonsArea, self, 'autocommit', 'Commit', box=False) commit_button.setFixedWidth(self.control_area_width) self.buttonsArea.layout().addWidget(commit_button)
def __init__(self, parent=None, windowTitle="Color Palette List", **kwargs): super().__init__(parent, windowTitle=windowTitle, **kwargs) self.setLayout(QVBoxLayout()) self.layout().setContentsMargins(0, 0, 0, 0) sa = QScrollArea( horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn ) sa.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) sa.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.layout().addWidget(sa) space = QWidget(self) space.setLayout(QVBoxLayout()) sa.setWidget(space) sa.setWidgetResizable(True) self.buttons = [] self.setMinimumWidth(400) box = gui.vBox(space, "Information", addSpace=True) gui.widgetLabel( box, '<p align="center">This dialog shows a list of predefined ' 'color palettes <br>from colorbrewer.org that can be used ' 'in Orange.<br/>You can select a palette by clicking on it.</p>' ) box = gui.vBox(space, "Default Palette", addSpace=True) butt = _ColorButton( DefaultRGBColors, flat=True, toolTip="Default color palette", clicked=self._buttonClicked ) box.layout().addWidget(butt) self.buttons.append(butt) for type in ["Qualitative", "Spectral", "Diverging", "Sequential", "Pastels"]: colorGroup = colorbrewer.colorSchemes.get(type.lower(), {}) if colorGroup: box = gui.vBox(space, type + " Palettes", addSpace=True) items = sorted(colorGroup.items()) for key, colors in items: butt = _ColorButton(colors, self, toolTip=key, flat=True, clicked=self._buttonClicked) box.layout().addWidget(butt) self.buttons.append(butt) buttons = QDialogButtonBox( QDialogButtonBox.Cancel, rejected=self.reject ) self.layout().addWidget(buttons) self.selectedColors = None
def init_form(self): vlayout = QVBoxLayout() if _api.USED_API == _api.QT_API_PYQT5: vlayout.setContentsMargins(0, 0, 0, 0) elif _api.USED_API == _api.QT_API_PYQT4: vlayout.setMargin(0) self.setLayout(vlayout) self._scroll = QScrollBar(QtCore.Qt.Horizontal) scrollarea = QScrollArea() scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) self._events_widget = EventsWidget(scroll=self._scroll) scrollarea.setWidget(self._events_widget) self._scroll.actionTriggered.connect(self.__scroll_changed) vlayout.addWidget(scrollarea) # The timeline widget vlayout.addWidget(self._scroll) # Add scroll self._scroll.setMaximum(0) self._scroll.setSliderPosition(0)
def __scrollAreaKeyPressEvent(self, event): modifiers = event.modifiers() if modifiers & QtCore.Qt.ControlModifier: event.ignore() if modifiers & QtCore.Qt.ShiftModifier: event.ignore() if modifiers & QtCore.Qt.AltModifier: event.ignore() if event.isAccepted(): QScrollArea.keyPressEvent(self._scrollArea, event)
def __init__(self, parent=None, **kwargs): # type: (Optional[QWidget], Any)-> None super().__init__(parent, **kwargs) self.__pages = [] # type: List[_ToolBoxPage] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = QScrollArea( self, objectName="toolbox-scroll-area", sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding), horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, widgetResizable=True, ) sb = ScrollBar() sb.styleChange.connect(self.updateGeometry) self.__scrollArea.setVerticalScrollBar(sb) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") self.__contentsLayout = _ToolBoxLayout( sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize, spacing=0) self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = QActionGroup( self, objectName="toolbox-tab-action-group", ) self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToggled)
def eventFilter(self, obj, event): if obj is self.widget() and event.type() == QEvent.Resize: if event.size() == event.oldSize() and self.widgetResizable(): # This is driving me insane. This should not have happened. # Before the event is sent QWidget specifically makes sure the # sizes are different, but somehow I still get this, and enter # an infinite recursion if I enter QScrollArea.eventFilter. # I can only duplicate this on one development machine a # Mac OSX using fink and Qt 4.7.3 return False return QScrollArea.eventFilter(self, obj, event)
def __init__(self, parent=None): super().__init__(self, parent) self.data = None self.partitions = [] self.matrix = None self.split_groups = [] self._disable_updates = False ######## # GUI ######## box = gui.widgetBox(self.controlArea, "Input") self.info_box = gui.widgetLabel(box, "No data on input\n") box = gui.widgetBox(self.controlArea, "Separate By", addSpace=True) self.separate_view = QListView(selectionMode=QListView.MultiSelection) box.layout().addWidget(self.separate_view) box = gui.widgetBox(self.controlArea, "Sort By", addSpace=True) self.relevant_view = QListView(selectionMode=QListView.MultiSelection) box.layout().addWidget(self.relevant_view) self.distance_view = gui.comboBox( self.controlArea, self, "distance_measure", box="Distance Measure", items=[name for name, _ in self.DISTANCE_FUNCTIONS]) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") self.groups_box = gui.widgetBox(self.mainArea, "Groups") self.groups_scroll_area = QScrollArea() self.groups_box.layout().addWidget(self.groups_scroll_area)
def __init__(self, parent=None): super().__init__(parent) self.corpus = None self.initial_ngram_range = None # initial range of input corpus — used for inplace self.preprocessor = preprocess.Preprocessor() # -- INFO -- info_box = gui.widgetBox(self.controlArea, 'Info') info_box.setFixedWidth(self.control_area_width) self.controlArea.layout().addStretch() self.info_label = gui.label(info_box, self, '') self.update_info() # -- PIPELINE -- frame = QFrame() frame.setContentsMargins(0, 0, 0, 0) frame.setFrameStyle(QFrame.Box) frame.setStyleSheet('.QFrame { border: 1px solid #B3B3B3; }') frame_layout = QVBoxLayout() frame_layout.setContentsMargins(0, 0, 0, 0) frame_layout.setSpacing(0) frame.setLayout(frame_layout) self.stages = [] for stage in self.preprocessors: widget = stage(self) self.stages.append(widget) setattr(self, stage.attribute, widget) frame_layout.addWidget(widget) widget.change_signal.connect(self.settings_invalidated) frame_layout.addStretch() self.scroll = QScrollArea() self.scroll.setWidget(frame) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.resize(frame_layout.sizeHint()) self.scroll.setMinimumHeight(500) self.set_minimal_width() self.mainArea.layout().addWidget(self.scroll) # Buttons area self.report_button.setFixedWidth(self.control_area_width) commit_button = gui.auto_commit(self.buttonsArea, self, 'autocommit', 'Commit', box=False) commit_button.setFixedWidth(self.control_area_width - 5) self.buttonsArea.layout().addWidget(commit_button)
def __init__(self, parent=None): super().__init__(self, parent) self.data = None self.partitions = [] self.matrix = None self.split_groups = [] self._disable_updates = False ######## # GUI ######## box = gui.widgetBox(self.controlArea, "Input") self.info_box = gui.widgetLabel(box, "No data on input\n") box = gui.widgetBox(self.controlArea, "Separate By", addSpace=True) self.separate_view = QListView( selectionMode=QListView.MultiSelection ) box.layout().addWidget(self.separate_view) box = gui.widgetBox(self.controlArea, "Sort By", addSpace=True) self.relevant_view = QListView( selectionMode=QListView.MultiSelection) box.layout().addWidget(self.relevant_view) self.distance_view = gui.comboBox( self.controlArea, self, "distance_measure", box="Distance Measure", items=[name for name, _ in self.DISTANCE_FUNCTIONS]) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") self.groups_box = gui.widgetBox(self.mainArea, "Groups") self.groups_scroll_area = QScrollArea() self.groups_box.layout().addWidget(self.groups_scroll_area)
class OWPreprocess(OWWidget): name = 'Preprocess Text' description = 'Construct a text pre-processing pipeline.' icon = 'icons/TextPreprocess.svg' priority = 30 class Inputs: corpus = Input("Corpus", Corpus) class Outputs: corpus = Output("Corpus", Corpus) autocommit = settings.Setting(True) preprocessors = [ TransformationModule, TokenizerModule, NormalizationModule, FilteringModule, NgramsModule, POSTaggingModule, ] transformers = settings.SettingProvider(TransformationModule) tokenizer = settings.SettingProvider(TokenizerModule) normalizer = settings.SettingProvider(NormalizationModule) filters = settings.SettingProvider(FilteringModule) ngrams_range = settings.SettingProvider(NgramsModule) pos_tagger = settings.SettingProvider(POSTaggingModule) control_area_width = 250 buttons_area_orientation = Qt.Vertical UserAdviceMessages = [ widget.Message( "Some preprocessing methods require data (like word relationships, stop words, " "punctuation rules etc.) from the NLTK package. This data was downloaded " "to: {}".format(nltk_data_dir()), "nltk_data") ] class Error(OWWidget.Error): stanford_tagger = Msg("Problem while loading Stanford POS Tagger\n{}") class Warning(OWWidget.Warning): no_token_left = Msg( 'No tokens on output! Please, change configuration.') def __init__(self, parent=None): super().__init__(parent) self.corpus = None self.initial_ngram_range = None # initial range of input corpus — used for inplace self.preprocessor = preprocess.Preprocessor() # -- INFO -- info_box = gui.widgetBox(self.controlArea, 'Info') info_box.setFixedWidth(self.control_area_width) self.controlArea.layout().addStretch() self.info_label = gui.label(info_box, self, '') self.update_info() # -- PIPELINE -- frame = QFrame() frame.setContentsMargins(0, 0, 0, 0) frame.setFrameStyle(QFrame.Box) frame.setStyleSheet('.QFrame { border: 1px solid #B3B3B3; }') frame_layout = QVBoxLayout() frame_layout.setContentsMargins(0, 0, 0, 0) frame_layout.setSpacing(0) frame.setLayout(frame_layout) self.stages = [] for stage in self.preprocessors: widget = stage(self) self.stages.append(widget) setattr(self, stage.attribute, widget) frame_layout.addWidget(widget) widget.change_signal.connect(self.settings_invalidated) frame_layout.addStretch() self.scroll = QScrollArea() self.scroll.setWidget(frame) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.resize(frame_layout.sizeHint()) self.scroll.setMinimumHeight(500) self.set_minimal_width() self.mainArea.layout().addWidget(self.scroll) # Buttons area self.report_button.setFixedWidth(self.control_area_width) commit_button = gui.auto_commit(self.buttonsArea, self, 'autocommit', 'Commit', box=False) commit_button.setFixedWidth(self.control_area_width - 5) self.buttonsArea.layout().addWidget(commit_button) @Inputs.corpus def set_data(self, data=None): self.corpus = data.copy() if data is not None else None self.initial_ngram_range = data.ngram_range if data is not None else None self.commit() def update_info(self, corpus=None): if corpus is not None: info = 'Document count: {}\n' \ 'Total tokens: {}\n'\ 'Total types: {}'\ .format(len(corpus), sum(map(len, corpus.tokens)), len(corpus.dictionary)) else: info = 'No corpus.' self.info_label.setText(info) def commit(self): self.Warning.no_token_left.clear() if self.corpus is not None: self.apply() else: self.update_info() self.Outputs.corpus.send(None) def apply(self): self.preprocess() @asynchronous def preprocess(self): for module in self.stages: setattr(self.preprocessor, module.attribute, module.value) self.corpus.pos_tags = None # reset pos_tags and ngrams_range self.corpus.ngram_range = self.initial_ngram_range return self.preprocessor(self.corpus, inplace=True, on_progress=self.on_progress) @preprocess.on_start def on_start(self): self.progressBarInit(None) @preprocess.callback def on_progress(self, i): self.progressBarSet(i, None) @preprocess.on_result def on_result(self, result): self.update_info(result) if result is not None and len(result.dictionary) == 0: self.Warning.no_token_left() result = None self.Outputs.corpus.send(result) self.progressBarFinished(None) def set_minimal_width(self): max_width = 250 for widget in self.stages: if widget.enabled: max_width = max(max_width, widget.sizeHint().width()) self.scroll.setMinimumWidth(max_width + 20) @pyqtSlot() def settings_invalidated(self): self.set_minimal_width() self.commit() def send_report(self): self.report_items('Preprocessor', self.preprocessor.report())
def __init__(self, prefs, parent): super(UserPrefUI, self).__init__() self.title = 'User Preferences' self.setLayout(QVBoxLayout(self)) self.parent = parent self.prefs = prefs # Init logger self.logger = Logger('userPrefsUI', 'UI : User Preferences') # Set up container container = QWidget() container.setLayout(QVBoxLayout()) # Set up scroll area scrollArea = QScrollArea() scrollArea.setWidget(container) scrollArea.setWidgetResizable(True) self.layout().addWidget(scrollArea) # Permanent controls self._saveButton = QPushButton('Save', self) self._saveButton.clicked.connect(self.__save) self._startMaximized = QCheckBox('Start Maximized', self) self._allowDuplicatePins = QCheckBox('Allow Duplicate Output Pins', self) self._enableOBA = QCheckBox('OnBoard Air', self) self._enableLighting = QCheckBox('Lighting', self) self._enableTracControl = QCheckBox('Traction Control', self) self._enableCamViewer = QCheckBox('Camera Viewer', self) self._enableGyro = QCheckBox('Inclinometer', self) self._panelLabel = QLabel( '<html><center>Enable Modules:<br>\ <em>Restart TacOS for changes to take effect.</em></center></html>', self) self._i2cBus = QComboBox(self) self._i2cBus.addItems(Config.busList) self._i2cBusLabel = QLabel('I2C Bus', self) self._i2cAddress = QLineEdit('I2C Address', self) self._i2cLabel = QLabel( '<html><center>I2C Parameters:<br>\ <em>Restart TacOS for changes to take effect.</em></center></html>', self) self._i2cDebug = QCheckBox('I2C Debug Mode', self) self._i2cDebugLabel = QLabel( '<html><center>I2C Debug Mode:<br>\ <em>Disables all I2C comms.<br>\ Restart TacOS for changes to take effect.</em></center></html>', self) self._debugLogging = QCheckBox('Enable Debug Logs', self) self._restartButton = QPushButton('Restart TacOS', self) self._restartButton.clicked.connect(self.__restart) # Set initial values for control in [ 'startMaximized', 'allowDuplicatePins', 'enableOBA', 'enableLighting', 'enableTracControl', 'enableCamViewer', 'enableGyro', 'i2cDebug', 'debugLogging' ]: if control in prefs.keys(): exec('self._%s.setChecked(prefs["%s"])' % (control, control)) else: if control in [ 'enableOBA', 'enableLighting', 'enableTracControl', 'enableCamViewer', 'enableGyro' ]: exec('self._%s.setChecked(True)' % control) else: exec('self._%s.setChecked(False)' % control) if 'i2cAddress' in prefs.keys(): self._i2cAddress.setText(prefs['i2cAddress']) else: self._i2cAddress.setText('0x20') if 'i2cBus' in prefs.keys(): self._i2cBus.setCurrentText(str(prefs['i2cBus'])) else: self._i2cBus.setCurrentIndex(0) layoutList = [[ '_startMaximized', '_allowDuplicatePins', '_debugLogging' ], ['_panelLabel'], ['_enableOBA', '_enableLighting', '_enableTracControl'], ['_enableCamViewer', '_enableGyro'], ['_i2cLabel'], ['_i2cBusLabel', '_i2cBus', '_i2cAddress'], ['_i2cDebugLabel'], ['_i2cDebug'], ['_saveButton', '_restartButton']] for i in layoutList: panel = QWidget() panel.setLayout(QHBoxLayout(panel)) panel.layout().setSpacing(20) panel.layout().setAlignment(Qt.AlignCenter) for c in i: panel.layout().addWidget(eval('self.%s' % c)) container.layout().addWidget(panel)
def __init__(self, parent_widget, parent_component): QObject.__init__(self) OWComponent.__init__(self, widget=parent_widget) box = gui.widgetBox(parent_component, margin=0) self.filter_full_text = gui.lineEdit( box, self, 'filter_by_full_text', label=self.FILTER_FULL_TEXT_LABEL, callback=self.on_filter_full_text_changed, ) self.toggle_animation = QParallelAnimationGroup() self.toggle_button = QToolButton() self.toggle_button.setCheckable(True) self.toggle_button.setChecked(False) self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.setText(self.TOGGLE_BTN_LABEL) self.toggle_button.setStyleSheet('QToolButton {border: none; padding-top: 5px; }') self.toggle_button.setIconSize(QSize(15, 15)) self.toggle_button.pressed.connect(self.on_toggle) self.collapsible_components = QScrollArea() self.collapsible_components.setMaximumHeight(0) self.collapsible_components.setMinimumHeight(0) self.collapsible_components.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.collapsible_components.setFrameShape(QFrame.NoFrame) box = gui.widgetBox(parent_component, margin=0) box.layout().addWidget(self.toggle_button) box.layout().addWidget(self.collapsible_components) self.toggle_animation.addAnimation(QPropertyAnimation(box, b"minimumHeight")) self.toggle_animation.addAnimation(QPropertyAnimation(box, b"maximumHeight")) self.toggle_animation.addAnimation(QPropertyAnimation(self.collapsible_components, b"maximumHeight")) layout = QHBoxLayout() left_box = gui.widgetBox(None, self, margin=0, flat=True) mid_box = gui.widgetBox(None, self, margin=0, flat=True) right_box = gui.widgetBox(None, self, margin=0, flat=True) self.filter_name = gui.lineEdit( left_box, self, 'filter_by_name', label=self.FILTER_NAME_LABEL, callback=self.on_filter_changed, addSpace=5 ) self.filter_contrib = gui.lineEdit( mid_box, self, 'filter_by_contrib', label=self.FILTER_CONTRIB_LABEL, callback=self.on_filter_changed, addSpace=5, ) self.filter_owner = gui.lineEdit( right_box, self, 'filter_by_owner', label=self.FILTER_OWNER_LABEL, callback=self.on_filter_changed, addSpace=5, ) self.filter_modified = gui.comboBox( left_box, self, 'filter_by_modified', label=self.FILTER_MODIFIED_LABEL, callback=self.on_filter_changed, items=FilterByDateModified.labels(), ) self.sorting = gui.comboBox( mid_box, self, 'sort_by', label=self.SORTING_LABEL, callback=self.on_filter_changed, items=SortBy.labels() ) gui.rubber(left_box) gui.rubber(mid_box) gui.rubber(right_box) layout.addWidget(left_box) layout.addWidget(mid_box) layout.addWidget(right_box) self.collapsible_components.setLayout(layout) collapsed_height = box.layout().sizeHint().height() - self.collapsible_components.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(100) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1) content_animation.setDuration(100) content_animation.setStartValue(0) content_animation.setEndValue(content_height)
class CollapsibleFilterComponent(OWComponent, QObject): options_changed = pyqtSignal() filter_by_full_text: str = settings.Setting('', schema_only=True) filter_by_name: str = settings.Setting('', schema_only=True) filter_by_contrib: str = settings.Setting('', schema_only=True) filter_by_owner: str = settings.Setting('', schema_only=True) filter_by_modified: int = settings.Setting(FilterByDateModified.any_time, schema_only=True) sort_by: int = settings.Setting(SortBy.newest_first, schema_only=True) FILTER_FULL_TEXT_LABEL = 'Search' TOGGLE_BTN_LABEL = 'Narrow your search' FILTER_NAME_LABEL = 'Filter by name' FILTER_CONTRIB_LABEL = 'Filter by contributor' FILTER_OWNER_LABEL = 'Filter by owner' FILTER_MODIFIED_LABEL = 'Filter by date modified' SORTING_LABEL = 'Sorting' def __init__(self, parent_widget, parent_component): QObject.__init__(self) OWComponent.__init__(self, widget=parent_widget) box = gui.widgetBox(parent_component, margin=0) self.filter_full_text = gui.lineEdit( box, self, 'filter_by_full_text', label=self.FILTER_FULL_TEXT_LABEL, callback=self.on_filter_full_text_changed, ) self.toggle_animation = QParallelAnimationGroup() self.toggle_button = QToolButton() self.toggle_button.setCheckable(True) self.toggle_button.setChecked(False) self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setArrowType(Qt.RightArrow) self.toggle_button.setText(self.TOGGLE_BTN_LABEL) self.toggle_button.setStyleSheet('QToolButton {border: none; padding-top: 5px; }') self.toggle_button.setIconSize(QSize(15, 15)) self.toggle_button.pressed.connect(self.on_toggle) self.collapsible_components = QScrollArea() self.collapsible_components.setMaximumHeight(0) self.collapsible_components.setMinimumHeight(0) self.collapsible_components.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.collapsible_components.setFrameShape(QFrame.NoFrame) box = gui.widgetBox(parent_component, margin=0) box.layout().addWidget(self.toggle_button) box.layout().addWidget(self.collapsible_components) self.toggle_animation.addAnimation(QPropertyAnimation(box, b"minimumHeight")) self.toggle_animation.addAnimation(QPropertyAnimation(box, b"maximumHeight")) self.toggle_animation.addAnimation(QPropertyAnimation(self.collapsible_components, b"maximumHeight")) layout = QHBoxLayout() left_box = gui.widgetBox(None, self, margin=0, flat=True) mid_box = gui.widgetBox(None, self, margin=0, flat=True) right_box = gui.widgetBox(None, self, margin=0, flat=True) self.filter_name = gui.lineEdit( left_box, self, 'filter_by_name', label=self.FILTER_NAME_LABEL, callback=self.on_filter_changed, addSpace=5 ) self.filter_contrib = gui.lineEdit( mid_box, self, 'filter_by_contrib', label=self.FILTER_CONTRIB_LABEL, callback=self.on_filter_changed, addSpace=5, ) self.filter_owner = gui.lineEdit( right_box, self, 'filter_by_owner', label=self.FILTER_OWNER_LABEL, callback=self.on_filter_changed, addSpace=5, ) self.filter_modified = gui.comboBox( left_box, self, 'filter_by_modified', label=self.FILTER_MODIFIED_LABEL, callback=self.on_filter_changed, items=FilterByDateModified.labels(), ) self.sorting = gui.comboBox( mid_box, self, 'sort_by', label=self.SORTING_LABEL, callback=self.on_filter_changed, items=SortBy.labels() ) gui.rubber(left_box) gui.rubber(mid_box) gui.rubber(right_box) layout.addWidget(left_box) layout.addWidget(mid_box) layout.addWidget(right_box) self.collapsible_components.setLayout(layout) collapsed_height = box.layout().sizeHint().height() - self.collapsible_components.maximumHeight() content_height = layout.sizeHint().height() for i in range(self.toggle_animation.animationCount()): animation = self.toggle_animation.animationAt(i) animation.setDuration(100) animation.setStartValue(collapsed_height) animation.setEndValue(collapsed_height + content_height) content_animation = self.toggle_animation.animationAt(self.toggle_animation.animationCount() - 1) content_animation.setDuration(100) content_animation.setStartValue(0) content_animation.setEndValue(content_height) def on_toggle(self): """ Start animation """ checked = self.toggle_button.isChecked() self.toggle_button.setArrowType(Qt.DownArrow if not checked else Qt.RightArrow) self.toggle_animation.setDirection(QAbstractAnimation.Forward if not checked else QAbstractAnimation.Backward) self.toggle_animation.start() def on_filter_full_text_changed(self): self.sort_by = SortBy.relevance if self.filter_full_text else SortBy.newest_first self.on_filter_changed() def on_filter_changed(self): self.options_changed.emit()
def __init__(self): super().__init__() self.left = Config.geometry[0] self.top = Config.geometry[1] self.width = Config.geometry[2] self.height = Config.geometry[3] self.setLayout(QHBoxLayout(self)) # Init logger self.logger = Logger('mainUI', 'UI : Main') # Read in user prefs self.prefs = {} self.loadPrefs() # Load configured objects self.lights = Lights() self.lights.load() self.obas = OBAs() self.obas.load() self.tracs = Tracs() self.tracs.load() # Init I2C control if 'i2cAddress' in self.prefs.keys(): _address = self.prefs['i2cAddress'] else: _address = Config.defaultI2CAddress if 'i2cBus' in self.prefs.keys(): _bus = self.prefs['i2cBus'] else: _bus = Config.defaultI2CBus if 'i2cDebug' in self.prefs.keys(): _debug = self.prefs['i2cDebug'] else: _debug = False self._i2cBus = I2CBus(int(_bus), str(_address), bool(_debug)) del _bus, _address, _debug # Create menu frame self._sidebarMenu = QFrame(self) self._sidebarMenu.layout = QVBoxLayout(self._sidebarMenu) self._sidebarMenu.setMaximumWidth(Config.menuWidth + 50) self._sidebarMenu.setMaximumHeight(Config.geometry[3]) self._sidebarMenu.setStyleSheet( "QFrame{border-right: 1px solid black}") # Create menu _container = QWidget() _container.layout = QVBoxLayout() _container.setLayout(_container.layout) _scroll = QScrollArea() _scroll.setWidget(_container) _scroll.setWidgetResizable(True) self._sidebarMenu.layout.addWidget(_scroll) for _key in [ 'enableOBA', 'enableLighting', 'enableTracControl', 'enableCamViewer', 'enableGyro' ]: if _key in self.prefs.keys(): if self.prefs[_key]: _title = { 'enableOBA': 'control_oba', 'enableLighting': 'control_light', 'enableTracControl': 'control_trac', 'enableCamViewer': 'control_cam', 'enableGyro': 'control_gyro' }[_key] _icon = { 'enableOBA': Config.faIcon("wind"), 'enableLighting': Config.faIcon("lightbulb"), 'enableTracControl': Config.icon("tracControl", "rearDiff")['path'], 'enableCamViewer': Config.faIcon("camera"), 'enableGyro': Config.faIcon("truck-pickup") }[_key] _button = MenuButton(panel=_title, parent=self) _button.setIcon(QIcon(_icon)) _container.layout.addWidget(_button) _settingsButton = MenuButton(panel="config_prefs", parent=self) _settingsButton.setIcon(QIcon(Config.faIcon("user-cog"))) _container.layout.addWidget(_settingsButton) del _container, _scroll, _key, _settingsButton, _title, _icon, _button # Create version info label self._version = QLabel('v%s' % Config.version, self) self._version.setAlignment(Qt.AlignCenter) self._version.setFixedWidth(Config.menuWidth) self._version.setStyleSheet("QLabel{border: none}") self._sidebarMenu.layout.addWidget(self._version) # Create OSK button self._oskButton = QPushButton('', self) self._oskButton.setIcon(QIcon(Config.faIcon('keyboard'))) self._oskButton.setFixedWidth(Config.menuWidth) self._oskButton.clicked.connect(self.showOSK) self._sidebarMenu.layout.addWidget(self._oskButton) # Add menu frame to main UI self.layout().addWidget(self._sidebarMenu) # Create main UI panel self._mainPanel = QWidget(self) self._mainPanel.setLayout(QVBoxLayout(self._mainPanel)) self.layout().addWidget(self._mainPanel) # Init default UI for _key in [ 'enableOBA', 'enableLighting', 'enableTracControl', 'enableCamViewer', 'enableGyro' ]: _cUI = None _uiName = None if _key in self.prefs.keys(): if self.prefs[_key]: if _key == 'enableOBA': _cUI = OBAControlUI(self.obas.obas, self) _uiName = 'control_oba' elif _key == 'enableLighting': _cUI = LightControlUI(self.lights.lights, self) _uiName = 'control_light' elif _key == 'enableTracControl': _cUI = TracControlUI(self.tracs.tracs, self) _uiName = 'control_trac' elif _key == 'enableCamViewer': _cUI = CamViewer(0) _uiName = 'control_cam' elif _key == 'enableGryo': _cUI = Gyrometer() _uiName = 'control_gyro' if _cUI is not None: break if _cUI is None: _cUI = UserPrefUI(self.prefs, parent=self) _uiName = 'config_prefs' self._currentUI = {'name': _uiName, 'obj': _cUI} _cUI.setParent(self) self._mainPanel.layout().addWidget(_cUI) _cUI.show() # Create button panel self._btnPanel = QWidget(self) self._btnPanel.setLayout(QHBoxLayout(self._btnPanel)) self._mainPanel.layout().addWidget(self._btnPanel) # Create Config button self._configButton = QPushButton('Configure', self) self._configButton.setFixedHeight(50) self._configButton.setIcon(QIcon(Config.faIcon('cog'))) self._configButton.clicked.connect(self.__configButtonAction) self._btnPanel.layout().addWidget(self._configButton) # Create Night Mode button self._nightModeButton = QPushButton('', self) self._nightModeButton.setFixedHeight(50) self._nightModeButton.setIcon( QIcon({ True: Config.faIcon('sun'), False: Config.faIcon('moon') }[self.prefs['nightMode']])) self._nightModeButton.setText({ True: 'Day Mode', False: 'Night Mode' }[self.prefs['nightMode']]) self._nightModeButton.clicked.connect(self.toggleNightMode) self._btnPanel.layout().addWidget(self._nightModeButton) self.setNightMode(self.prefs['nightMode'])
class OWPreprocess(widget.OWWidget, openclass=True): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 keywords = ["process"] settings_version = 2 class Inputs: data = Input("Data", Orange.data.Table) class Outputs: preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False) preprocessed_data = Output("Preprocessed Data", Orange.data.Table) storedsettings = Setting({}) autocommit = Setting(True) PREPROCESSORS = PREPROCESS_ACTIONS CONTROLLER = Controller def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") gui.rubber(self.controlArea) # we define a class that lets us set the vertical sizeHint # based on the height and number of items in the list # see self.__update_list_sizeHint class ListView(QListView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.vertical_hint = None def sizeHint(self): sh = super().sizeHint() if self.vertical_hint: return QSize(sh.width(), self.vertical_hint) return sh self.preprocessorsView = view = ListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = { ppdef.qualname: ppdef for ppdef in self.PREPROCESSORS } # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = self.CONTROLLER(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) gui.auto_apply(self.buttonsArea, self, "autocommit") self._initialize() def _initialize(self): for pp_def in self.PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) self.__update_list_sizeHint() model = self.load(self.storedsettings) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def __update_list_sizeHint(self): view = self.preprocessorsView h = view.sizeHintForRow(0) n = self.preprocessors.rowCount() view.vertical_hint = n * h + 2 # only on Mac? view.updateGeometry() def load(self, saved): """Load a preprocessor list from a dict.""" preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, _column, _parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model @staticmethod def save(model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @Inputs.data @check_sql_input def set_data(self, data=None): """Set the input dataset.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.Outputs.preprocessor.send(preprocessor) self.Outputs.preprocessed_data.send(data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() @classmethod def migrate_settings(cls, settings, version): if version < 2: for action, params in settings["storedsettings"]["preprocessors"]: if action == "orange.preprocess.scale": scale = center = None if "center" in params: center = params.pop("center").name if "scale" in params: scale = params.pop("scale").name migratable = { ("Mean", "NoScaling"): Scale.CenterByMean, ("NoCentering", "Std"): Scale.ScaleBySD, ("Mean", "Std"): Scale.NormalizeBySD, ("NoCentering", "Span"): Scale.NormalizeBySpan_ZeroBased } params["method"] = \ migratable.get((center, scale), Scale.NormalizeBySD) def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if pp: self.report_items("Settings", pp)
def init_form(self): # Get the current path of the file rootPath = os.path.dirname(__file__) vlayout = QVBoxLayout() hlayout = QHBoxLayout() if _api.USED_API == _api.QT_API_PYQT5: hlayout.setContentsMargins(0, 0, 0, 0) vlayout.setContentsMargins(0, 0, 0, 0) elif _api.USED_API == _api.QT_API_PYQT4: hlayout.setMargin(0) vlayout.setMargin(0) self.setLayout(vlayout) # Add scroll area scrollarea = QScrollArea() self._scrollArea = scrollarea scrollarea.setMinimumHeight(140) scrollarea.setWidgetResizable(True) scrollarea.keyPressEvent = self.__scrollAreaKeyPressEvent scrollarea.keyReleaseEvent = self.__scrollAreaKeyReleaseEvent vlayout.addWidget(scrollarea) # The timeline widget self._time = widget = TimelineWidget(self) widget._scroll = scrollarea scrollarea.setWidget(widget) # Timeline zoom slider slider = QSlider(QtCore.Qt.Horizontal) slider.setFocusPolicy(QtCore.Qt.NoFocus) slider.setMinimum(1) slider.setMaximum(100) slider.setValue(10) slider.setPageStep(1) slider.setTickPosition(QSlider.NoTicks) # TicksBothSides slider.valueChanged.connect(self.__scaleSliderChange) slider_label_zoom_in = QLabel() slider_label_zoom_out = QLabel() slider_label_zoom_in.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_IN) slider_label_zoom_out.setPixmap( conf.PYFORMS_PIXMAP_EVENTTIMELINE_ZOOM_OUT) self._zoomLabel = QLabel("100%") hlayout.addWidget(self._zoomLabel) hlayout.addWidget(slider_label_zoom_out) hlayout.addWidget(slider) hlayout.addWidget(slider_label_zoom_in) # Import/Export Buttons btn_import = QPushButton("Import") btn_import.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_IMPORT) btn_import.clicked.connect(self.__open_import_win_evt) btn_export = QPushButton("Export") btn_export.setIcon(conf.PYFORMS_ICON_EVENTTIMELINE_EXPORT) btn_export.clicked.connect(self.__export) hlayout.addWidget(btn_import) hlayout.addWidget(btn_export) vlayout.addLayout(hlayout)
class OWPreprocess(widget.OWWidget): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 class Inputs: data = Input("Data", Orange.data.Table) class Outputs: preprocessor = Output("Preprocessor", preprocess.preprocess.Preprocess, dynamic=False) preprocessed_data = Output("Preprocessed Data", Orange.data.Table) storedsettings = settings.Setting({}) autocommit = settings.Setting(True) def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly ) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn ) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize() def _initialize(self): for pp_def in PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) try: model = self.load(self.storedsettings) except Exception: model = self.load({}) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def load(self, saved): """Load a preprocessor list from a dict.""" name = saved.get("name", "") preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, column, parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model def save(self, model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @Inputs.data @check_sql_input def set_data(self, data=None): """Set the input data set.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.Outputs.preprocessor.send(preprocessor) self.Outputs.preprocessed_data.send(data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(sh.width() + 300, 500)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if len(pp): self.report_items("Settings", pp)
class OWGenotypeDistances(widget.OWWidget): name = "Expression Profile Distances" description = ("Compute distances between expression profiles of " "different experimental factors.") icon = "../widgets/icons/GenotypeDistances.svg" priority = 1050 inputs = [("Data", Orange.data.Table, "set_data")] outputs = [("Distances", Orange.misc.DistMatrix), ("Sorted Data", Orange.data.Table)] settingsHandler = SetContextHandler() separate_keys = settings.ContextSetting({}) relevant_keys = settings.ContextSetting({}) distance_measure = settings.Setting(0) auto_commit = settings.Setting(False) DISTANCE_FUNCTIONS = [ ("Distance from Pearson correlation", dist_pcorr), ("Euclidean distance", dist_eucl), ("Distance from Spearman correlation", dist_spearman) ] def __init__(self, parent=None): super().__init__(self, parent) self.data = None self.partitions = [] self.matrix = None self.split_groups = [] self._disable_updates = False ######## # GUI ######## box = gui.widgetBox(self.controlArea, "Input") self.info_box = gui.widgetLabel(box, "No data on input\n") box = gui.widgetBox(self.controlArea, "Separate By", addSpace=True) self.separate_view = QListView( selectionMode=QListView.MultiSelection ) box.layout().addWidget(self.separate_view) box = gui.widgetBox(self.controlArea, "Sort By", addSpace=True) self.relevant_view = QListView( selectionMode=QListView.MultiSelection) box.layout().addWidget(self.relevant_view) self.distance_view = gui.comboBox( self.controlArea, self, "distance_measure", box="Distance Measure", items=[name for name, _ in self.DISTANCE_FUNCTIONS]) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") self.groups_box = gui.widgetBox(self.mainArea, "Groups") self.groups_scroll_area = QScrollArea() self.groups_box.layout().addWidget(self.groups_scroll_area) def sizeHint(self): return QSize(800, 600) def clear(self): self.data = None self.partitions = [] self.split_groups = [] self.matrix = None def get_suitable_keys(self, data): """Return suitable attr label keys from the data where the key has at least two unique values in the data. """ attrs = [attr.attributes.items() for attr in data.domain.attributes] attrs = reduce(operator.iadd, attrs, []) # in case someone put non string values in attributes dict attrs = [(str(key), str(value)) for key, value in attrs] attrs = set(attrs) values = defaultdict(set) for key, value in attrs: values[key].add(value) keys = [key for key in values if len(values[key]) > 1] return keys def set_data(self, data=None): """Set the input data table. """ self.closeContext() self.clear() self.error(0) self.warning(0) if data and not self.get_suitable_keys(data): self.error(0, "Data has no suitable column labels.") data = None self.data = data if data: self.info_box.setText("{0} genes\n{1} experiments" .format(len(data), len(data.domain))) self.update_control() self.split_data() else: self.separate_view.setModel(itemmodels.PyListModel([])) self.relevant_view.setModel(itemmodels.PyListModel([])) self.groups_scroll_area.setWidget(QWidget()) self.info_box.setText("No data on input.\n") self.commit() def update_control(self): """Update the control area of the widget. Populate the list views with keys from attribute labels. """ keys = self.get_suitable_keys(self.data) model = itemmodels.PyListModel(keys) self.separate_view.setModel(model) self.separate_view.selectionModel().selectionChanged.connect( self.on_separate_key_changed) model = itemmodels.PyListModel(keys) self.relevant_view.setModel(model) self.relevant_view.selectionModel().selectionChanged.connect( self.on_relevant_key_changed) self.openContext(keys) # Get the selected keys from the open context separate_keys = self.separate_keys relevant_keys = self.relevant_keys def select(model, selection_model, selected_items): all_items = list(model) try: indices = [all_items.index(item) for item in selected_items] except: indices = [] selection = QItemSelection() for ind in indices: index = model.index(ind) selection.select(index, index) selection_model.select(selection, QItemSelectionModel.Select) self._disable_updates = True try: select(self.relevant_view.model(), self.relevant_view.selectionModel(), relevant_keys) select(self.separate_view.model(), self.separate_view.selectionModel(), separate_keys) finally: self._disable_updates = False def on_separate_key_changed(self, *args): if not self._disable_updates: self.separate_keys = self.selected_separeate_by_keys() self.split_data() def on_relevant_key_changed(self, *args): if not self._disable_updates: self.relevant_keys = self.selected_relevant_keys() self.split_data() def selected_separeate_by_keys(self): """Return the currently selected separate by keys """ rows = self.separate_view.selectionModel().selectedRows() rows = sorted([idx.row() for idx in rows]) keys = [self.separate_view.model()[row] for row in rows] return keys def selected_relevant_keys(self): """Return the currently selected relevant keys """ rows = self.relevant_view.selectionModel().selectedRows() rows = sorted([idx.row() for idx in rows]) keys = [self.relevant_view.model()[row] for row in rows] return keys def split_data(self): """Split the data and update the Groups widget """ separate_keys = self.selected_separeate_by_keys() relevant_keys = self.selected_relevant_keys() self.warning(0) if not separate_keys: self.warning(0, "No separate by column selected.") partitions, uniquepos = separate_by( self.data, separate_keys, consider=relevant_keys) partitions = partitions.items() all_values = defaultdict(set) for a in [at.attributes for at in self.data.domain.attributes]: for k, v in a.items(): all_values[k].add(v) # sort groups pkeys = [key for key, _ in partitions] types = [data_type([a[i] for a in pkeys]) for i in range(len(pkeys[0]))] partitions = sorted(partitions, key=lambda x: tuple(types[i](v) for i,v in enumerate(x[0]))) split_groups = [] # Collect relevant key value pairs for all columns relevant_items = None for keys, indices in partitions: if relevant_items == None: relevant_items = [defaultdict(set) for _ in indices] for i, ind in enumerate(indices): if ind is not None: attr = self.data.domain[ind] for key in relevant_keys: relevant_items[i][key].add(attr.attributes[key]) #those with different values between rows are not relevant for d in relevant_items: for k, s in list(d.items()): if len(s) > 1: del d[k] else: d[k] = s.pop() def get_attr(attr_index, i): if attr_index is None: attr = Orange.data.ContinuousVariable(next(missing_name_gen), compute_value=lambda x: None) attr.attributes.update(relevant_items[i]) return attr else: return self.data.domain[attr_index] for keys, indices in partitions: attrs = [get_attr(attr_index, i) for i, attr_index in enumerate(indices)] for attr in attrs: attr.attributes.update(zip(separate_keys, keys)) domain = Orange.data.Domain(attrs, [], self.data.domain.metas) split_groups.append((keys, domain)) self.set_groups(separate_keys, split_groups, relevant_keys, relevant_items, all_values, uniquepos) self.partitions = partitions self.split_groups = split_groups self.commit() def set_groups(self, keys, groups, relevant_keys, relevant_items, all_values, uniquepos): """Set the current data groups and update the Group widget """ layout = QVBoxLayout() header_widths = [] header_views = [] palette = self.palette() all_values = all_values.keys() def for_print(rd): attrs = [] for d in rd: attr = Orange.data.ContinuousVariable(next(inactive_name_gen)) attr.attributes.update(d) attrs.append(attr) return Orange.data.Domain(attrs, None) for separatev, domain in [(None, for_print(relevant_items))] + groups: label = None if separatev is not None: ann_vals = " <b>|</b> ".join(["<b>{0}</b> = {1}".format(key,val) \ for key, val in zip(keys, separatev)]) label = QLabel(ann_vals) model = QStandardItemModel() for i, attr in enumerate(domain.attributes): item = QStandardItem() if separatev is not None: isunique = uniquepos[separatev][i] else: isunique = all(a[i] for a in uniquepos.values()) if str(attr.name).startswith("!!missing "): # TODO: Change this to not depend on name header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key not in relevant_items[i]] header_text = "\n".join(header_text) if header_text else "Empty" item.setData(header_text, Qt.DisplayRole) item.setFlags(Qt.NoItemFlags) item.setData(QColor(Qt.red), Qt.ForegroundRole) item.setData(palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole) item.setData("Missing feature.", Qt.ToolTipRole) elif str(attr.name).startswith("!!inactive "): header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key in relevant_items[i]] header_text = "\n".join(header_text) if header_text else "No descriptor" item.setData(header_text, Qt.DisplayRole) item.setData(palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole) else: header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key not in relevant_items[i]] header_text = "\n".join(header_text) if header_text else "Empty" item.setData(header_text, Qt.DisplayRole) item.setData(attr.name, Qt.ToolTipRole) if not isunique: item.setData(QColor(Qt.red), Qt.ForegroundRole) model.setHorizontalHeaderItem(i, item) attr_count = len(domain.attributes) view = MyHeaderView(Qt.Horizontal) view.setResizeMode(QHeaderView.Fixed) view.setModel(model) hint = view.sizeHint() view.setMaximumHeight(hint.height()) widths = [view.sectionSizeHint(i) for i in range(attr_count)] header_widths.append(widths) header_views.append(view) if label: layout.addWidget(label) layout.addWidget(view) layout.addSpacing(8) # Make all header sections the same width width_sum = 0 max_header_count = max([h.count() for h in header_views]) for i in range(max_header_count): max_width = max([w[i] for w in header_widths if i < len(w)] or [0]) for view in header_views: if i < view.count(): view.resizeSection(i, max_width) width_sum += max_width + 2 for h in header_views: h.setMinimumWidth(h.length() + 4) widget = QWidget() widget.setLayout(layout) widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) layout.activate() max_width = max(h.length() for h in header_views) + 20 left, _, right, _ = self.getContentsMargins() widget.setMinimumWidth(width_sum) widget.setMinimumWidth(max_width + left + right) self.groups_scroll_area.setWidget(widget) def compute_distances(self, separate_keys, partitions, data): """Compute the distances between genotypes. """ if separate_keys and partitions: self.progressBarInit() # matrix = Orange.misc.DistMatrix(len(partitions)) matrix = numpy.zeros((len(partitions), len(partitions))) profiles = [linearize(data, indices) for _, indices in partitions] dist_func = self.DISTANCE_FUNCTIONS[self.distance_measure][1] # from Orange.utils import progress_bar_milestones count = (len(profiles) * len(profiles) - 1) / 2 # milestones = progress_bar_milestones(count) iter_count = 0 for i in range(len(profiles)): for j in range(i + 1, len(profiles)): matrix[i, j] = dist_func(profiles[i], profiles[j]) matrix[j, i] = matrix[i, j] iter_count += 1 # if iter_count in milestones: self.progressBarSet(100.0 * iter_count / count) self.progressBarFinished() items = [["{0}={1}".format(key, value) for key, value in zip(separate_keys, values)] for values, _ in partitions] items = [" | ".join(item) for item in items] # matrix.setattr("items", items) matrix = Orange.misc.DistMatrix(matrix) else: matrix = None self.matrix = matrix def commit(self): separate_keys = self.selected_separeate_by_keys() self.compute_distances(separate_keys, self.partitions, self.data) if self.split_groups: all_attrs = [] for group, domain in self.split_groups: attrs = [] group_name = " | ".join("{0}={1}".format(*item) for item in zip(separate_keys, group)) for attr in domain.attributes: newattr = clone_attr(attr) newattr.attributes["<GENOTYPE GROUP>"] = group_name # Need a better way to pass the groups to downstream widgets. attrs.append(newattr) all_attrs.extend(attrs) domain = Orange.data.Domain(all_attrs, self.data.domain.class_vars, self.data.domain.metas) data = Orange.data.Table(domain, self.data) else: data = None self.send("Sorted Data", data) self.send("Distances", self.matrix) def send_report(self): self.report_items(( ("Separate By", ", ".join(self.selected_separeate_by_keys())), ("Sort By", ", ".join(self.selected_relevant_keys())), ("Distance Measure", self.DISTANCE_FUNCTIONS[self.distance_measure][0]) )) layout = self.groups_scroll_area.widget().layout() html = "<table>" for i in range(layout.count()): item = layout.itemAt(i) if isinstance(item, QSpacerItem): html += "<tr><td></td></tr>" elif isinstance(item, QWidgetItem): hor = item.widget() if isinstance(hor, QLabel): label = hor.text() html += "<tr><td><b>%s</b></td></tr>" % label elif isinstance(hor, QHeaderView): model = hor.model() content = (model.horizontalHeaderItem(col) for col in range(model.columnCount())) content = (item.text().replace('\n', "<br/>") for item in content) html += "<tr>" + ''.join("<td>{}</td>".format(item) for item in content) + "</tr>" html += "</table>" self.report_raw("Groups", html)
def __init__(self, serverSettings): super().__init__() self.css = """ QPushButton {background-color: #1588c5; color: white; height: 20px; border: 1px solid black; border-radius: 2px;} QPushButton:hover {background-color: #1555f5; } QPushButton:hover:pressed { background-color: #1588c5; color: black; border-style: inset; border: 1px solid white} QPushButton:disabled { background-color: lightGray; border: 1px solid gray; } """ self.addRemoveCSS = """ QPushButton {background-color: lightBlue; color: white; height: 20px; border: 1px solid black; border-radius: 2px;} QPushButton:hover {background-color: blue; } QPushButton:hover:pressed { background-color: lightBlue; color: black; border-style: inset; border: 1px solid white} QPushButton:disabled { background-color: white; border: 1px solid gray; } """ self.setMinimumSize(640, 384) self.serverSettings = serverSettings self.setWindowTitle("Edit server settings") self.settingsFile = "/biodepot/serverSettings.json" self.addIcon = QtGui.QIcon("/icons/add.png") self.removeIcon = QtGui.QIcon("/icons/remove.png") self.table = TableWidgetDragRows() self.table.setColumnCount(3) self.table.horizontalHeader().setResizeMode( 0, QtGui.QHeaderView.ResizeToContents) self.table.horizontalHeader().setResizeMode( 1, QtGui.QHeaderView.ResizeToContents) self.table.horizontalHeader().setResizeMode(2, QtGui.QHeaderView.Stretch) self.table.horizontalHeader().setStretchLastSection(True) self.table.setHorizontalHeaderLabels( ["Server IP", "Threads", "Volume map"]) # start with blank slate - and keep temp copy of serverSettings # only update when dialog is done and if the self.serverSettingsCopy = {} try: self.importServers(filename=self.settingsFile) except Exception as e: self.serverSettingsCopy = {} if "data" not in self.serverSettingsCopy: self.serverSettingsCopy["data"] = OrderedDict() nRows = len(self.serverSettingsCopy["data"].keys()) if nRows: self.table.setRowCount(nRows) rowNum = 0 for addr in self.serverSettingsCopy["data"]: # sys.stderr.write('addr {} data {}\n'.format(addr,serverSettingsCopy['data'][addr])) self.table.setItem(rowNum, 0, QTableWidgetItem(addr)) self.table.setItem( rowNum, 1, QTableWidgetItem( self.serverSettingsCopy["data"][addr]["maxThreads"]), ) self.table.setItem( rowNum, 2, QTableWidgetItem(self.serverSettingsCopy["data"][addr] ["volumeMapping"]), ) rowNum = rowNum + 1 # buttons for save and load loadBtn = gui.button(None, self, "Load", callback=self.importServers) loadBtn.setStyleSheet(self.css) loadBtn.setFixedSize(70, 20) saveBtn = gui.button(None, self, "Save", callback=self.save) saveBtn.setStyleSheet(self.css) saveBtn.setFixedSize(70, 20) saveAsBtn = gui.button(None, self, "Save As", callback=self.exportServers) saveAsBtn.setStyleSheet(self.css) saveAsBtn.setFixedSize(70, 20) addBtn = gui.button(None, self, "", callback=self.addRow) removeBtn = gui.button(None, self, "", callback=self.removeRow) addBtn.setIcon(self.addIcon) addBtn.setStyleSheet(self.addRemoveCSS) removeBtn.setIcon(self.removeIcon) removeBtn.setStyleSheet(self.addRemoveCSS) # table tableBox = QGroupBox() scroll_area = QScrollArea(verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) scroll_area.setWidget(tableBox) scroll_area.setWidgetResizable(True) tableLayout = QHBoxLayout() tableLayout.addWidget(self.table) tableBox.setLayout(tableLayout) # buttons buttonLayout = QHBoxLayout() buttonLayout.addWidget(loadBtn) buttonLayout.addWidget(saveBtn) buttonLayout.addWidget(saveAsBtn) buttonLayout.addStretch(1) buttonLayout.addWidget(addBtn) buttonLayout.addWidget(removeBtn) serverLayout = QVBoxLayout() serverLayout.addWidget(tableBox) serverLayout.addLayout(buttonLayout) self.setLayout(serverLayout) removeBtn.setEnabled( bool(len(self.table.selectionModel().selectedRows()))) self.table.itemSelectionChanged.connect(lambda: removeBtn.setEnabled( bool(len(self.table.selectionModel().selectedRows()))))
class OWGenotypeDistances(widget.OWWidget): name = "Expression Profile Distances" description = ("Compute distances between expression profiles of " "different experimental factors.") icon = "../widgets/icons/GenotypeDistances.svg" priority = 1050 inputs = [("Data", Orange.data.Table, "set_data")] outputs = [("Distances", Orange.misc.DistMatrix), ("Sorted Data", Orange.data.Table)] settingsHandler = SetContextHandler() separate_keys = settings.ContextSetting({}) relevant_keys = settings.ContextSetting({}) distance_measure = settings.Setting(0) auto_commit = settings.Setting(False) DISTANCE_FUNCTIONS = [("Distance from Pearson correlation", dist_pcorr), ("Euclidean distance", dist_eucl), ("Distance from Spearman correlation", dist_spearman) ] def __init__(self, parent=None): super().__init__(self, parent) self.data = None self.partitions = [] self.matrix = None self.split_groups = [] self._disable_updates = False ######## # GUI ######## box = gui.widgetBox(self.controlArea, "Input") self.info_box = gui.widgetLabel(box, "No data on input\n") box = gui.widgetBox(self.controlArea, "Separate By", addSpace=True) self.separate_view = QListView(selectionMode=QListView.MultiSelection) box.layout().addWidget(self.separate_view) box = gui.widgetBox(self.controlArea, "Sort By", addSpace=True) self.relevant_view = QListView(selectionMode=QListView.MultiSelection) box.layout().addWidget(self.relevant_view) self.distance_view = gui.comboBox( self.controlArea, self, "distance_measure", box="Distance Measure", items=[name for name, _ in self.DISTANCE_FUNCTIONS]) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") self.groups_box = gui.widgetBox(self.mainArea, "Groups") self.groups_scroll_area = QScrollArea() self.groups_box.layout().addWidget(self.groups_scroll_area) def sizeHint(self): return QSize(800, 600) def clear(self): self.data = None self.partitions = [] self.split_groups = [] self.matrix = None def get_suitable_keys(self, data): """Return suitable attr label keys from the data where the key has at least two unique values in the data. """ attrs = [attr.attributes.items() for attr in data.domain.attributes] attrs = reduce(operator.iadd, attrs, []) # in case someone put non string values in attributes dict attrs = [(str(key), str(value)) for key, value in attrs] attrs = set(attrs) values = defaultdict(set) for key, value in attrs: values[key].add(value) keys = [key for key in values if len(values[key]) > 1] return keys def set_data(self, data=None): """Set the input data table. """ self.closeContext() self.clear() self.error(0) self.warning(0) if data and not self.get_suitable_keys(data): self.error(0, "Data has no suitable column labels.") data = None self.data = data if data: self.info_box.setText("{0} genes\n{1} experiments".format( len(data), len(data.domain))) self.update_control() self.split_data() else: self.separate_view.setModel(itemmodels.PyListModel([])) self.relevant_view.setModel(itemmodels.PyListModel([])) self.groups_scroll_area.setWidget(QWidget()) self.info_box.setText("No data on input.\n") self.commit() def update_control(self): """Update the control area of the widget. Populate the list views with keys from attribute labels. """ keys = self.get_suitable_keys(self.data) model = itemmodels.PyListModel(keys) self.separate_view.setModel(model) self.separate_view.selectionModel().selectionChanged.connect( self.on_separate_key_changed) model = itemmodels.PyListModel(keys) self.relevant_view.setModel(model) self.relevant_view.selectionModel().selectionChanged.connect( self.on_relevant_key_changed) self.openContext(keys) # Get the selected keys from the open context separate_keys = self.separate_keys relevant_keys = self.relevant_keys def select(model, selection_model, selected_items): all_items = list(model) try: indices = [all_items.index(item) for item in selected_items] except: indices = [] selection = QItemSelection() for ind in indices: index = model.index(ind) selection.select(index, index) selection_model.select(selection, QItemSelectionModel.Select) self._disable_updates = True try: select(self.relevant_view.model(), self.relevant_view.selectionModel(), relevant_keys) select(self.separate_view.model(), self.separate_view.selectionModel(), separate_keys) finally: self._disable_updates = False def on_separate_key_changed(self, *args): if not self._disable_updates: self.separate_keys = self.selected_separeate_by_keys() self.split_data() def on_relevant_key_changed(self, *args): if not self._disable_updates: self.relevant_keys = self.selected_relevant_keys() self.split_data() def selected_separeate_by_keys(self): """Return the currently selected separate by keys """ rows = self.separate_view.selectionModel().selectedRows() rows = sorted([idx.row() for idx in rows]) keys = [self.separate_view.model()[row] for row in rows] return keys def selected_relevant_keys(self): """Return the currently selected relevant keys """ rows = self.relevant_view.selectionModel().selectedRows() rows = sorted([idx.row() for idx in rows]) keys = [self.relevant_view.model()[row] for row in rows] return keys def split_data(self): """Split the data and update the Groups widget """ separate_keys = self.selected_separeate_by_keys() relevant_keys = self.selected_relevant_keys() self.warning(0) if not separate_keys: self.warning(0, "No separate by column selected.") partitions, uniquepos = separate_by(self.data, separate_keys, consider=relevant_keys) partitions = partitions.items() all_values = defaultdict(set) for a in [at.attributes for at in self.data.domain.attributes]: for k, v in a.items(): all_values[k].add(v) # sort groups pkeys = [key for key, _ in partitions] types = [ data_type([a[i] for a in pkeys]) for i in range(len(pkeys[0])) ] partitions = sorted(partitions, key=lambda x: tuple(types[i](v) for i, v in enumerate(x[0]))) split_groups = [] # Collect relevant key value pairs for all columns relevant_items = None for keys, indices in partitions: if relevant_items == None: relevant_items = [defaultdict(set) for _ in indices] for i, ind in enumerate(indices): if ind is not None: attr = self.data.domain[ind] for key in relevant_keys: relevant_items[i][key].add(attr.attributes[key]) #those with different values between rows are not relevant for d in relevant_items: for k, s in list(d.items()): if len(s) > 1: del d[k] else: d[k] = s.pop() def get_attr(attr_index, i): if attr_index is None: attr = Orange.data.ContinuousVariable( next(missing_name_gen), compute_value=lambda x: None) attr.attributes.update(relevant_items[i]) return attr else: return self.data.domain[attr_index] for keys, indices in partitions: attrs = [ get_attr(attr_index, i) for i, attr_index in enumerate(indices) ] for attr in attrs: attr.attributes.update(zip(separate_keys, keys)) domain = Orange.data.Domain(attrs, [], self.data.domain.metas) split_groups.append((keys, domain)) self.set_groups(separate_keys, split_groups, relevant_keys, relevant_items, all_values, uniquepos) self.partitions = partitions self.split_groups = split_groups self.commit() def set_groups(self, keys, groups, relevant_keys, relevant_items, all_values, uniquepos): """Set the current data groups and update the Group widget """ layout = QVBoxLayout() header_widths = [] header_views = [] palette = self.palette() all_values = all_values.keys() def for_print(rd): attrs = [] for d in rd: attr = Orange.data.ContinuousVariable(next(inactive_name_gen)) attr.attributes.update(d) attrs.append(attr) return Orange.data.Domain(attrs, None) for separatev, domain in [(None, for_print(relevant_items))] + groups: label = None if separatev is not None: ann_vals = " <b>|</b> ".join(["<b>{0}</b> = {1}".format(key,val) \ for key, val in zip(keys, separatev)]) label = QLabel(ann_vals) model = QStandardItemModel() for i, attr in enumerate(domain.attributes): item = QStandardItem() if separatev is not None: isunique = uniquepos[separatev][i] else: isunique = all(a[i] for a in uniquepos.values()) if str(attr.name).startswith( "!!missing " ): # TODO: Change this to not depend on name header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key not in relevant_items[i]] header_text = "\n".join( header_text) if header_text else "Empty" item.setData(header_text, Qt.DisplayRole) item.setFlags(Qt.NoItemFlags) item.setData(QColor(Qt.red), Qt.ForegroundRole) item.setData( palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole) item.setData("Missing feature.", Qt.ToolTipRole) elif str(attr.name).startswith("!!inactive "): header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key in relevant_items[i]] header_text = "\n".join( header_text) if header_text else "No descriptor" item.setData(header_text, Qt.DisplayRole) item.setData( palette.color(QPalette.Disabled, QPalette.Window), Qt.BackgroundRole) else: header_text = ["{0}={1}".format(key, attr.attributes.get(key, "?")) \ for key in all_values if key not in relevant_items[i]] header_text = "\n".join( header_text) if header_text else "Empty" item.setData(header_text, Qt.DisplayRole) item.setData(attr.name, Qt.ToolTipRole) if not isunique: item.setData(QColor(Qt.red), Qt.ForegroundRole) model.setHorizontalHeaderItem(i, item) attr_count = len(domain.attributes) view = MyHeaderView(Qt.Horizontal) view.setResizeMode(QHeaderView.Fixed) view.setModel(model) hint = view.sizeHint() view.setMaximumHeight(hint.height()) widths = [view.sectionSizeHint(i) for i in range(attr_count)] header_widths.append(widths) header_views.append(view) if label: layout.addWidget(label) layout.addWidget(view) layout.addSpacing(8) # Make all header sections the same width width_sum = 0 max_header_count = max([h.count() for h in header_views]) for i in range(max_header_count): max_width = max([w[i] for w in header_widths if i < len(w)] or [0]) for view in header_views: if i < view.count(): view.resizeSection(i, max_width) width_sum += max_width + 2 for h in header_views: h.setMinimumWidth(h.length() + 4) widget = QWidget() widget.setLayout(layout) widget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) layout.activate() max_width = max(h.length() for h in header_views) + 20 left, _, right, _ = self.getContentsMargins() widget.setMinimumWidth(width_sum) widget.setMinimumWidth(max_width + left + right) self.groups_scroll_area.setWidget(widget) def compute_distances(self, separate_keys, partitions, data): """Compute the distances between genotypes. """ if separate_keys and partitions: self.progressBarInit() # matrix = Orange.misc.DistMatrix(len(partitions)) matrix = numpy.zeros((len(partitions), len(partitions))) profiles = [linearize(data, indices) for _, indices in partitions] dist_func = self.DISTANCE_FUNCTIONS[self.distance_measure][1] # from Orange.utils import progress_bar_milestones count = (len(profiles) * len(profiles) - 1) / 2 # milestones = progress_bar_milestones(count) iter_count = 0 for i in range(len(profiles)): for j in range(i + 1, len(profiles)): matrix[i, j] = dist_func(profiles[i], profiles[j]) matrix[j, i] = matrix[i, j] iter_count += 1 # if iter_count in milestones: self.progressBarSet(100.0 * iter_count / count) self.progressBarFinished() items = [[ "{0}={1}".format(key, value) for key, value in zip(separate_keys, values) ] for values, _ in partitions] items = [" | ".join(item) for item in items] # matrix.setattr("items", items) matrix = Orange.misc.DistMatrix(matrix) else: matrix = None self.matrix = matrix def commit(self): separate_keys = self.selected_separeate_by_keys() self.compute_distances(separate_keys, self.partitions, self.data) if self.split_groups: all_attrs = [] for group, domain in self.split_groups: attrs = [] group_name = " | ".join("{0}={1}".format(*item) for item in zip(separate_keys, group)) for attr in domain.attributes: newattr = clone_attr(attr) newattr.attributes[ "<GENOTYPE GROUP>"] = group_name # Need a better way to pass the groups to downstream widgets. attrs.append(newattr) all_attrs.extend(attrs) domain = Orange.data.Domain(all_attrs, self.data.domain.class_vars, self.data.domain.metas) data = Orange.data.Table(domain, self.data) else: data = None self.send("Sorted Data", data) self.send("Distances", self.matrix) def send_report(self): self.report_items( (("Separate By", ", ".join(self.selected_separeate_by_keys())), ("Sort By", ", ".join(self.selected_relevant_keys())), ("Distance Measure", self.DISTANCE_FUNCTIONS[self.distance_measure][0]))) layout = self.groups_scroll_area.widget().layout() html = "<table>" for i in range(layout.count()): item = layout.itemAt(i) if isinstance(item, QSpacerItem): html += "<tr><td></td></tr>" elif isinstance(item, QWidgetItem): hor = item.widget() if isinstance(hor, QLabel): label = hor.text() html += "<tr><td><b>%s</b></td></tr>" % label elif isinstance(hor, QHeaderView): model = hor.model() content = (model.horizontalHeaderItem(col) for col in range(model.columnCount())) content = (item.text().replace('\n', "<br/>") for item in content) html += "<tr>" + ''.join("<td>{}</td>".format(item) for item in content) + "</tr>" html += "</table>" self.report_raw("Groups", html)
def __init__(self): super().__init__() self.query_edits = [] self.remove_buttons = [] # GUI scrollArea = QScrollArea() #### header head_box = gui.hBox(self.controlArea) head_box.setMaximumHeight(200) info_box = gui.widgetBox(head_box, 'Info') self.info = gui.widgetLabel(info_box, ("Import and/or create queries\n\n" + "If a Table with a dictionary is connected as input,\n" + "it can be imported, in which case multiple query\n" + "terms with the same label will be combined.\n\n" + "If the label is a number, or if negative weights are\n" + "used (e.g. sentiment dictionaries), the query will be\n" + "split into positive and negative parts.\n\n" "If a row in the dictionary contains multiple terms\n" + "separated with spaces, it will be seen as a phrase\n" + "(by adding quotes). This can be turned of in case\n" + "rows are already boolean queries.")) ## from input input_box = gui.widgetBox(head_box, "Create queries from dictionary file") input_box.setMaximumWidth(350) gui.button(input_box, self, 'multiple words are phrases', toggleButton=True, value='add_quotes', buttonType=QCheckBox) inputline_box = gui.hBox(input_box) inputline_box.setMinimumHeight(70) gui.listBox(inputline_box, self, 'query_in', labels='querytable_vars', box = 'Query column', callback=self.update_if_sync) gui.listBox(inputline_box, self, 'label_in', labels='querytable_vars2', box = 'Label column', callback=self.update_if_sync) self.weight_select = gui.listBox(inputline_box, self, 'weight_in', labels='querytable_vars2', box = 'Weight column', callback=self.update_if_sync) input_button_box = gui.hBox(input_box) gui.button(input_button_box, self, 'Keep synchronized', self.sync_on_off, toggleButton=True, value='sync', buttonType=QCheckBox) gui.button(input_button_box, self, 'Import', self.import_queries) gui.button(input_button_box, self, 'Append', self.append_queries) ## query field query_box = gui.widgetBox(self.controlArea) self.queries_box = QGridLayout() query_box.layout().addLayout(self.queries_box) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidget(query_box) querygridbox = gui.widgetBox(self.controlArea, 'Query') querygridbox.layout().addWidget(scroll) querygridbox.setMinimumHeight(200) querygridbox.setMinimumWidth(500) gui.rubber(query_box) self.queries_box.setColumnMinimumWidth(0, 5) self.queries_box.setColumnMinimumWidth(1, 60) self.queries_box.setColumnMinimumWidth(2, 350) self.queries_box.setColumnStretch(1, 0) self.queries_box.setColumnStretch(2, 100) self.queries_box.addWidget(QLabel("Label"), 0, 1) self.queries_box.addWidget(QLabel("Query"), 0, 2) self.update_queries() gui.button(query_box, self, "add query", callback=self.add_row, autoDefault=False) ## buttons scarybuttonbox = gui.hBox(self.controlArea) scarybuttonbox.layout().setAlignment(Qt.AlignRight) gui.button(scarybuttonbox, self, "remove all queries", callback=self.remove_all, width=150) QTimer.singleShot(0, self.send_queries) ## for send on startup
class OWPreprocess(OWWidget): name = '文本预处理' description = '构建文本预处理的管道' icon = 'icons/TextPreprocess.svg' priority = 200 class Inputs: corpus = Input("Corpus", Corpus) class Outputs: corpus = Output("Corpus", Corpus) autocommit = settings.Setting(True) preprocessors = [ TransformationModule, TokenizerModule, NormalizationModule, FilteringModule, NgramsModule, POSTaggingModule, ] transformers = settings.SettingProvider(TransformationModule) tokenizer = settings.SettingProvider(TokenizerModule) normalizer = settings.SettingProvider(NormalizationModule) filters = settings.SettingProvider(FilteringModule) ngrams_range = settings.SettingProvider(NgramsModule) pos_tagger = settings.SettingProvider(POSTaggingModule) control_area_width = 250 buttons_area_orientation = Qt.Vertical UserAdviceMessages = [ widget.Message("部分预处理所需要的数据(例如词汇关系、停用词、标点符号规则等)是从NLTK包中获取的,", "这些数据可以从{}下载。".format(nltk_data_dir())) ] class Error(OWWidget.Error): stanford_tagger = Msg("无法加载Stanford POS Tagger\n{}") stopwords_encoding = Msg("停用词表编码不正确,请使用 UTF-8 再试一次。") lexicon_encoding = Msg("词典编码不正确,请使用 UTF-8 再试一次。") error_reading_stopwords = Msg("读取文件错误: {}") error_reading_lexicon = Msg("读取文件错误: {}") class Warning(OWWidget.Warning): no_token_left = Msg('没有标记输出,请重新配置') udpipe_offline = Msg('没有网络连接,UDPipe 只加载本地模型') udpipe_offline_no_models = Msg('没有网络连接,UDPipe无本地模型') def __init__(self, parent=None): super().__init__(parent) self.corpus = None self.initial_ngram_range = None # initial range of input corpus — used for inplace self.preprocessor = preprocess.Preprocessor() # -- INFO -- info_box = gui.widgetBox(self.controlArea, '基本信息') info_box.setFixedWidth(self.control_area_width) self.controlArea.layout().addStretch() self.info_label = gui.label(info_box, self, '') self.update_info() # -- PIPELINE -- frame = QFrame() frame.setContentsMargins(0, 0, 0, 0) frame.setFrameStyle(QFrame.Box) frame.setStyleSheet('.QFrame { border: 1px solid #B3B3B3; }') frame_layout = QVBoxLayout() frame_layout.setContentsMargins(0, 0, 0, 0) frame_layout.setSpacing(0) frame.setLayout(frame_layout) self.stages = [] for stage in self.preprocessors: widget = stage(self) self.stages.append(widget) setattr(self, stage.attribute, widget) frame_layout.addWidget(widget) widget.change_signal.connect(self.settings_invalidated) frame_layout.addStretch() self.scroll = QScrollArea() self.scroll.setWidget(frame) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.resize(frame_layout.sizeHint()) self.scroll.setMinimumHeight(500) self.set_minimal_width() self.mainArea.layout().addWidget(self.scroll) # Buttons area self.report_button.setFixedWidth(self.control_area_width) commit_button = gui.auto_commit(self.buttonsArea, self, 'autocommit', '提交', '自动提交', box=False) commit_button.setFixedWidth(self.control_area_width - 5) self.buttonsArea.layout().addWidget(commit_button) @Inputs.corpus def set_data(self, data=None): self.corpus = data.copy() if data is not None else None self.initial_ngram_range = data.ngram_range if data is not None else None self.commit() def update_info(self, corpus=None): if corpus is not None: info = '文档数量: {}\n' \ '标记数量: {}\n'\ '类型数量: {}'\ .format(len(corpus), sum(map(len, corpus.tokens)), len(corpus.dictionary)) else: info = '没有数据集' self.info_label.setText(info) def commit(self): self.Warning.no_token_left.clear() if self.corpus is not None: self.apply() else: self.update_info() self.Outputs.corpus.send(None) def apply(self): self.preprocess() @asynchronous def preprocess(self): for module in self.stages: setattr(self.preprocessor, module.attribute, module.value) self.corpus.pos_tags = None # reset pos_tags and ngrams_range self.corpus.ngram_range = self.initial_ngram_range return self.preprocessor(self.corpus, inplace=True, on_progress=self.on_progress) @preprocess.on_start def on_start(self): self.progressBarInit(None) @preprocess.callback def on_progress(self, i): self.progressBarSet(i, None) @preprocess.on_result def on_result(self, result): self.update_info(result) if result is not None and len(result.dictionary) == 0: self.Warning.no_token_left() result = None self.Outputs.corpus.send(result) self.progressBarFinished(None) def set_minimal_width(self): max_width = 250 for widget in self.stages: if widget.enabled: max_width = max(max_width, widget.sizeHint().width()) self.scroll.setMinimumWidth(max_width + 20) @pyqtSlot() def settings_invalidated(self): self.set_minimal_width() self.commit() def send_report(self): self.report_items('Preprocessor', self.preprocessor.report())
class OWPreprocess(widget.OWWidget): name = "Preprocess" description = "Construct a data preprocessing pipeline." icon = "icons/Preprocess.svg" priority = 2105 inputs = [("Data", Orange.data.Table, "set_data")] outputs = [("Preprocessor", preprocess.preprocess.Preprocess), ("Preprocessed Data", Orange.data.Table)] storedsettings = settings.Setting({}) autocommit = settings.Setting(True) def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize() def _initialize(self): for pp_def in PREPROCESSORS: description = pp_def.description if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item = QStandardItem(icon, description.title) item.setToolTip(description.summary or "") item.setData(pp_def, DescriptionRole) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled) self.preprocessors.appendRow([item]) try: model = self.load(self.storedsettings) except Exception: model = self.load({}) self.set_model(model) if not model.rowCount(): # enforce default width constraint if no preprocessors # are instantiated (if the model is not empty the constraints # will be triggered by LayoutRequest event on the `flow_view`) self.__update_size_constraint() self.apply() def load(self, saved): """Load a preprocessor list from a dict.""" name = saved.get("name", "") preprocessors = saved.get("preprocessors", []) model = StandardItemModel() def dropMimeData(data, action, row, column, parent): if data.hasFormat("application/x-qwidget-ref") and \ action == Qt.CopyAction: qname = bytes(data.data("application/x-qwidget-ref")).decode() ppdef = self._qname2ppdef[qname] item = QStandardItem(ppdef.description.title) item.setData({}, ParametersRole) item.setData(ppdef.description.title, Qt.DisplayRole) item.setData(ppdef, DescriptionRole) self.preprocessormodel.insertRow(row, [item]) return True else: return False model.dropMimeData = dropMimeData for qualname, params in preprocessors: pp_def = self._qname2ppdef[qualname] description = pp_def.description item = QStandardItem(description.title) if description.icon: icon = QIcon(description.icon) else: icon = QIcon() item.setIcon(icon) item.setToolTip(description.summary) item.setData(pp_def, DescriptionRole) item.setData(params, ParametersRole) model.appendRow(item) return model def save(self, model): """Save the preprocessor list to a dict.""" d = {"name": ""} preprocessors = [] for i in range(model.rowCount()): item = model.item(i) pp_def = item.data(DescriptionRole) params = item.data(ParametersRole) preprocessors.append((pp_def.qualname, params)) d["preprocessors"] = preprocessors return d def set_model(self, ppmodel): if self.preprocessormodel: self.preprocessormodel.dataChanged.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsInserted.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsRemoved.disconnect( self.__on_modelchanged) self.preprocessormodel.rowsMoved.disconnect(self.__on_modelchanged) self.preprocessormodel.deleteLater() self.preprocessormodel = ppmodel self.controler.setModel(ppmodel) if ppmodel is not None: self.preprocessormodel.dataChanged.connect(self.__on_modelchanged) self.preprocessormodel.rowsInserted.connect(self.__on_modelchanged) self.preprocessormodel.rowsRemoved.connect(self.__on_modelchanged) self.preprocessormodel.rowsMoved.connect(self.__on_modelchanged) self.__update_overlay() def __update_overlay(self): if self.preprocessormodel is None or \ self.preprocessormodel.rowCount() == 0: self.overlay.setWidget(self.flow_view) self.overlay.show() else: self.overlay.setWidget(None) self.overlay.hide() def __on_modelchanged(self): self.__update_overlay() self.commit() @check_sql_input def set_data(self, data=None): """Set the input data set.""" self.data = data def handleNewSignals(self): self.apply() def __activated(self, index): item = self.preprocessors.itemFromIndex(index) action = item.data(DescriptionRole) item = QStandardItem() item.setData({}, ParametersRole) item.setData(action.description.title, Qt.DisplayRole) item.setData(action, DescriptionRole) self.preprocessormodel.appendRow([item]) def buildpreproc(self): plist = [] for i in range(self.preprocessormodel.rowCount()): item = self.preprocessormodel.item(i) desc = item.data(DescriptionRole) params = item.data(ParametersRole) if not isinstance(params, dict): params = {} create = desc.viewclass.createinstance plist.append(create(params)) if len(plist) == 1: return plist[0] else: return preprocess.preprocess.PreprocessorList(plist) def apply(self): # Sync the model into storedsettings on every apply. self.storeSpecificSettings() preprocessor = self.buildpreproc() if self.data is not None: self.error() try: data = preprocessor(self.data) except (ValueError, ZeroDivisionError) as e: self.error(str(e)) return else: data = None self.send("Preprocessor", preprocessor) self.send("Preprocessed Data", data) def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User)) def customEvent(self, event): if event.type() == QEvent.User and self._invalidated: self._invalidated = False self.apply() def eventFilter(self, receiver, event): if receiver is self.flow_view and event.type() == QEvent.LayoutRequest: QTimer.singleShot(0, self.__update_size_constraint) return super().eventFilter(receiver, event) def storeSpecificSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().storeSpecificSettings() def saveSettings(self): """Reimplemented.""" self.storedsettings = self.save(self.preprocessormodel) super().saveSettings() def onDeleteWidget(self): self.data = None self.set_model(None) super().onDeleteWidget() @Slot() def __update_size_constraint(self): # Update minimum width constraint on the scroll area containing # the 'instantiated' preprocessor list (to avoid the horizontal # scroll bar). sh = self.flow_view.minimumSizeHint() scroll_width = self.scroll_area.verticalScrollBar().width() self.scroll_area.setMinimumWidth( min(max(sh.width() + scroll_width + 2, self.controlArea.width()), 520)) def sizeHint(self): sh = super().sizeHint() return sh.expandedTo(QSize(sh.width() + 300, 500)) def send_report(self): pp = [(self.controler.model().index(i, 0).data(Qt.DisplayRole), w) for i, w in enumerate(self.controler.view.widgets())] if len(pp): self.report_items("Settings", pp)
def __scrollAreaKeyReleaseEvent(self, event): modifiers = int(event.modifiers()) self._time.keyReleaseEvent(event) QScrollArea.keyReleaseEvent(self._scrollArea, event)
def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") self.preprocessorsView = view = QListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly ) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = {ppdef.qualname: ppdef for ppdef in PREPROCESSORS} # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = Controller(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn ) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) box = gui.vBox(self.controlArea, "Output") gui.auto_commit(box, self, "autocommit", "Send", box=False) self._initialize()
def __init__(self, iterateSettings): nRows = len(iterateSettings["iterableAttrs"]) if not nRows: return nCols = 5 super().__init__() self.css = """ QPushButton {background-color: #1588c5; color: white; height: 20px; border: 1px solid black; border-radius: 2px;} QPushButton:hover {background-color: #1555f5; } QPushButton:hover:pressed { background-color: #1588c5; color: black; border-style: inset; border: 1px solid white} QPushButton:disabled { background-color: lightGray; border: 1px solid gray; } """ self.setMinimumSize(520, 240) self.iterateSettings = iterateSettings self.setWindowTitle("Edit iterate settings") self.table = QTableWidget() self.table.setColumnCount(nCols) for col in range(nCols - 1): self.table.horizontalHeader().setResizeMode( col, QtGui.QHeaderView.ResizeToContents) self.table.horizontalHeader().setResizeMode(nCols - 1, QtGui.QHeaderView.Stretch) self.table.horizontalHeader().setStretchLastSection(True) self.table.setHorizontalHeaderLabels([ "", "Parameter", "Group size", "Threads needed", "Required RAM (MB)" ]) self.settingsCopy = copy.deepcopy(self.iterateSettings) self.table.setRowCount(nRows) rowNum = 0 for parm in self.settingsCopy["iterableAttrs"]: # init values if they are not there if "data" not in self.settingsCopy: self.settingsCopy["data"] = {} if parm not in self.settingsCopy["data"]: self.settingsCopy["data"][parm] = { "groupSize": "1", "threads": "1", "ram": "0", } if ("groupSize" not in self.settingsCopy["data"][parm].keys() or not self.settingsCopy["data"][parm]["groupSize"]): self.settingsCopy["data"][parm]["groupSize"] = "1" if ("threads" not in self.settingsCopy["data"][parm].keys() or not self.settingsCopy["data"][parm]["threads"]): self.settingsCopy["data"][parm]["threads"] = "1" if ("ram" not in self.settingsCopy["data"][parm].keys() or not self.settingsCopy["data"][parm]["ram"]): self.settingsCopy["data"][parm]["ram"] = "0" # make column items cb = QTableWidgetItem() parmItem = QTableWidgetItem(parm) groupSizeItem = QTableWidgetItem( self.settingsCopy["data"][parm]["groupSize"]) threadItem = QTableWidgetItem( self.settingsCopy["data"][parm]["threads"]) ramItem = QTableWidgetItem(self.settingsCopy["data"][parm]["ram"]) self.setSelect(parmItem, False) parmItem.setFlags(parmItem.flags() ^ Qt.ItemIsEditable) parmItem.setFlags(parmItem.flags() ^ Qt.ItemIsSelectable) cb.setFlags(cb.flags() ^ Qt.ItemIsSelectable) if ("iteratedAttrs" in self.settingsCopy and parm in self.settingsCopy["iteratedAttrs"]): cb.setCheckState(QtCore.Qt.Checked) else: cb.setCheckState(QtCore.Qt.Unchecked) self.setEnable(parmItem, False) self.setEnableSelect(groupSizeItem, False) self.setEnableSelect(threadItem, False) self.setEnableSelect(ramItem, False) self.table.setItem(rowNum, 0, cb) self.table.setItem(rowNum, 1, parmItem) self.table.setItem(rowNum, 2, groupSizeItem) self.table.setItem(rowNum, 3, threadItem) self.table.setItem(rowNum, 4, ramItem) rowNum = rowNum + 1 self.table.cellChanged.connect(self.onCheckBoxChanged) # buttons for save and load saveBtn = gui.button(None, self, "Save", callback=self.save) saveBtn.setStyleSheet(self.css) saveBtn.setFixedSize(70, 20) # table tableBox = QGroupBox() scroll_area = QScrollArea(verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) scroll_area.setWidget(tableBox) scroll_area.setWidgetResizable(True) tableLayout = QHBoxLayout() tableLayout.addWidget(self.table) tableBox.setLayout(tableLayout) # buttons buttonLayout = QHBoxLayout() buttonLayout.setAlignment(Qt.AlignTop) buttonLayout.addWidget(saveBtn) iterateLayout = QVBoxLayout() iterateLayout.addWidget(tableBox) iterateLayout.addLayout(buttonLayout) self.setLayout(iterateLayout)
class OWPreprocess(OWWidget): name = 'Preprocess Text' description = 'Construct a text pre-processing pipeline.' icon = 'icons/TextPreprocess.svg' priority = 30 inputs = [(Input.CORPUS, Corpus, 'set_data')] outputs = [(Output.PP_CORPUS, Corpus)] autocommit = settings.Setting(True) preprocessors = [ TransformationModule, TokenizerModule, NormalizationModule, FilteringModule, NgramsModule, POSTaggingModule, ] transformers = settings.SettingProvider(TransformationModule) tokenizer = settings.SettingProvider(TokenizerModule) normalizer = settings.SettingProvider(NormalizationModule) filters = settings.SettingProvider(FilteringModule) ngrams_range = settings.SettingProvider(NgramsModule) pos_tagger = settings.SettingProvider(POSTaggingModule) control_area_width = 250 buttons_area_orientation = Qt.Vertical UserAdviceMessages = [ widget.Message( "Some preprocessing methods require data (like word relationships, stop words, " "punctuation rules etc.) from the NLTK package. This data, if you didn't have it " "already, was downloaded to: {}".format(Downloader().default_download_dir()), "nltk_data")] class Error(OWWidget.Error): stanford_tagger = Msg("Problem while loading Stanford POS Tagger\n{}") class Warning(OWWidget.Warning): no_token_left = Msg('No tokens on output! Please, change configuration.') def __init__(self, parent=None): super().__init__(parent) self.corpus = None self.initial_ngram_range = None # initial range of input corpus — used for inplace self.preprocessor = preprocess.Preprocessor() # -- INFO -- info_box = gui.widgetBox(self.controlArea, 'Info') info_box.setFixedWidth(self.control_area_width) self.controlArea.layout().addStretch() self.info_label = gui.label(info_box, self, '') self.update_info() # -- PIPELINE -- frame = QFrame() frame.setContentsMargins(0, 0, 0, 0) frame.setFrameStyle(QFrame.Box) frame.setStyleSheet('.QFrame { border: 1px solid #B3B3B3; }') frame_layout = QVBoxLayout() frame_layout.setContentsMargins(0, 0, 0, 0) frame_layout.setSpacing(0) frame.setLayout(frame_layout) self.stages = [] for stage in self.preprocessors: widget = stage(self) self.stages.append(widget) setattr(self, stage.attribute, widget) frame_layout.addWidget(widget) widget.change_signal.connect(self.settings_invalidated) frame_layout.addStretch() self.scroll = QScrollArea() self.scroll.setWidget(frame) self.scroll.setWidgetResizable(True) self.scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.scroll.resize(frame_layout.sizeHint()) self.scroll.setMinimumHeight(500) self.set_minimal_width() self.mainArea.layout().addWidget(self.scroll) # Buttons area self.report_button.setFixedWidth(self.control_area_width) commit_button = gui.auto_commit(self.buttonsArea, self, 'autocommit', 'Commit', box=False) commit_button.setFixedWidth(self.control_area_width - 5) self.buttonsArea.layout().addWidget(commit_button) def set_data(self, data=None): self.corpus = data.copy() if data is not None else None self.initial_ngram_range = data.ngram_range if data is not None else None self.commit() def update_info(self, corpus=None): if corpus is not None: info = 'Document count: {}\n' \ 'Total tokens: {}\n'\ 'Total types: {}'\ .format(len(corpus), sum(map(len, corpus.tokens)), len(corpus.dictionary)) else: info = 'No corpus.' self.info_label.setText(info) def commit(self): self.Warning.no_token_left.clear() if self.corpus is not None: self.apply() else: self.update_info() self.send(Output.PP_CORPUS, None) def apply(self): self.preprocess() @asynchronous def preprocess(self): for module in self.stages: setattr(self.preprocessor, module.attribute, module.value) self.corpus.pos_tags = None # reset pos_tags and ngrams_range self.corpus.ngram_range = self.initial_ngram_range return self.preprocessor(self.corpus, inplace=True, on_progress=self.on_progress) @preprocess.on_start def on_start(self): self.progressBarInit(None) @preprocess.callback def on_progress(self, i): self.progressBarSet(i, None) @preprocess.on_result def on_result(self, result): self.update_info(result) if result is not None and len(result.dictionary) == 0: self.Warning.no_token_left() result = None self.send(Output.PP_CORPUS, result) self.progressBarFinished(None) def set_minimal_width(self): max_width = 250 for widget in self.stages: if widget.enabled: max_width = max(max_width, widget.sizeHint().width()) self.scroll.setMinimumWidth(max_width + 20) @pyqtSlot() def settings_invalidated(self): self.set_minimal_width() self.commit() def send_report(self): self.report_items('Preprocessor', self.preprocessor.report())
def __init__(self): super().__init__() self.data = None self._invalidated = False # List of available preprocessors (DescriptionRole : Description) self.preprocessors = QStandardItemModel() def mimeData(indexlist): assert len(indexlist) == 1 index = indexlist[0] qname = index.data(DescriptionRole).qualname m = QMimeData() m.setData("application/x-qwidget-ref", qname.encode("utf-8")) return m # TODO: Fix this (subclass even if just to pass a function # for mimeData delegate) self.preprocessors.mimeData = mimeData box = gui.vBox(self.controlArea, "Preprocessors") gui.rubber(self.controlArea) # we define a class that lets us set the vertical sizeHint # based on the height and number of items in the list # see self.__update_list_sizeHint class ListView(QListView): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.vertical_hint = None def sizeHint(self): sh = super().sizeHint() if self.vertical_hint: return QSize(sh.width(), self.vertical_hint) return sh self.preprocessorsView = view = ListView( selectionMode=QListView.SingleSelection, dragEnabled=True, dragDropMode=QListView.DragOnly) view.setModel(self.preprocessors) view.activated.connect(self.__activated) box.layout().addWidget(view) #### self._qname2ppdef = { ppdef.qualname: ppdef for ppdef in self.PREPROCESSORS } # List of 'selected' preprocessors and their parameters. self.preprocessormodel = None self.flow_view = SequenceFlow() self.controler = self.CONTROLLER(self.flow_view, parent=self) self.overlay = OverlayWidget(self) self.overlay.setAttribute(Qt.WA_TransparentForMouseEvents) self.overlay.setWidget(self.flow_view) self.overlay.setLayout(QVBoxLayout()) self.overlay.layout().addWidget( QLabel("Drag items from the list on the left", wordWrap=True)) self.scroll_area = QScrollArea( verticalScrollBarPolicy=Qt.ScrollBarAlwaysOn) self.scroll_area.viewport().setAcceptDrops(True) self.scroll_area.setWidget(self.flow_view) self.scroll_area.setWidgetResizable(True) self.mainArea.layout().addWidget(self.scroll_area) self.flow_view.installEventFilter(self) gui.auto_apply(self.buttonsArea, self, "autocommit") self._initialize()
class ToolBox(QFrame): """ A tool box widget. """ # Signal emitted when a tab is toggled. tabToggled = Signal(int, bool) __exclusive = False # type: bool def setExclusive(self, exclusive): # type: (bool) -> None """ Set exclusive tabs (only one tab can be open at a time). """ if self.__exclusive != exclusive: self.__exclusive = exclusive self.__tabActionGroup.setExclusive(exclusive) checked = self.__tabActionGroup.checkedAction() if checked is None: # The action group can be out of sync with the actions state # when switching between exclusive states. actions_checked = [ page.action for page in self.__pages if page.action.isChecked() ] if actions_checked: checked = actions_checked[0] # Trigger/toggle remaining open pages if exclusive and checked is not None: for page in self.__pages: if checked != page.action and page.action.isChecked(): page.action.trigger() def exclusive(self): # type: () -> bool """ Are the tabs in the toolbox exclusive. """ return self.__exclusive exclusive_ = Property(bool, fget=exclusive, fset=setExclusive, designable=True, doc="Exclusive tabs") def __init__(self, parent=None, **kwargs): # type: (Optional[QWidget], Any)-> None super().__init__(parent, **kwargs) self.__pages = [] # type: List[_ToolBoxPage] self.__tabButtonHeight = -1 self.__tabIconSize = QSize() self.__exclusive = False layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) # Scroll area for the contents. self.__scrollArea = QScrollArea( self, objectName="toolbox-scroll-area", sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding), horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, widgetResizable=True, ) sb = ScrollBar() sb.styleChange.connect(self.updateGeometry) self.__scrollArea.setVerticalScrollBar(sb) self.__scrollArea.setFrameStyle(QScrollArea.NoFrame) # A widget with all of the contents. # The tabs/contents are placed in the layout inside this widget self.__contents = QWidget(self.__scrollArea, objectName="toolbox-contents") self.__contentsLayout = _ToolBoxLayout( sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize, spacing=0) self.__contentsLayout.setContentsMargins(0, 0, 0, 0) self.__contents.setLayout(self.__contentsLayout) self.__scrollArea.setWidget(self.__contents) layout.addWidget(self.__scrollArea) self.setLayout(layout) self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) self.__tabActionGroup = QActionGroup( self, objectName="toolbox-tab-action-group", ) self.__tabActionGroup.setExclusive(self.__exclusive) self.__actionMapper = QSignalMapper(self) self.__actionMapper.mapped[QObject].connect(self.__onTabActionToggled) def setTabButtonHeight(self, height): # type: (int) -> None """ Set the tab button height. """ if self.__tabButtonHeight != height: self.__tabButtonHeight = height for page in self.__pages: page.button.setFixedHeight(height) def tabButtonHeight(self): # type: () -> int """ Return the tab button height. """ return self.__tabButtonHeight def setTabIconSize(self, size): # type: (QSize) -> None """ Set the tab button icon size. """ if self.__tabIconSize != size: self.__tabIconSize = QSize(size) for page in self.__pages: page.button.setIconSize(size) def tabIconSize(self): # type: () -> QSize """ Return the tab icon size. """ return QSize(self.__tabIconSize) def tabButton(self, index): # type: (int) -> QAbstractButton """ Return the tab button at `index` """ return self.__pages[index].button def tabAction(self, index): # type: (int) -> QAction """ Return open/close action for the tab at `index`. """ return self.__pages[index].action def addItem(self, widget, text, icon=QIcon(), toolTip=""): # type: (QWidget, str, QIcon, str) -> int """ Append the `widget` in a new tab and return its index. Parameters ---------- widget : QWidget A widget to be inserted. The toolbox takes ownership of the widget. text : str Name/title of the new tab. icon : QIcon An icon for the tab button. toolTip : str Tool tip for the tab button. Returns ------- index : int Index of the inserted tab """ return self.insertItem(self.count(), widget, text, icon, toolTip) def insertItem(self, index, widget, text, icon=QIcon(), toolTip=""): # type: (int, QWidget, str, QIcon, str) -> int """ Insert the `widget` in a new tab at position `index`. See also -------- ToolBox.addItem """ button = self.createTabButton(widget, text, icon, toolTip) self.__contentsLayout.insertWidget(index * 2, button) self.__contentsLayout.insertWidget(index * 2 + 1, widget) widget.hide() page = _ToolBoxPage(index, widget, button.defaultAction(), button) self.__pages.insert(index, page) # update the indices __pages list for i in range(index + 1, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) self.__updatePositions() # Show (open) the first tab. if self.count() == 1 and index == 0: page.action.trigger() self.__updateSelected() self.updateGeometry() return index def removeItem(self, index): # type: (int) -> None """ Remove the widget at `index`. Note ---- The widget is hidden but is is not deleted. It is up to the caller to delete it. """ self.__contentsLayout.takeAt(2 * index + 1) self.__contentsLayout.takeAt(2 * index) page = self.__pages.pop(index) # Update the page indexes for i in range(index, self.count()): self.__pages[i] = self.__pages[i]._replace(index=i) page.button.deleteLater() # Hide the widget and reparent to self # This follows QToolBox.removeItem page.widget.hide() page.widget.setParent(self) self.__updatePositions() self.__updateSelected() self.updateGeometry() def count(self): # type: () -> int """ Return the number of widgets inserted in the toolbox. """ return len(self.__pages) def widget(self, index): # type: (int) -> QWidget """ Return the widget at `index`. """ return self.__pages[index].widget def createTabButton(self, widget, text, icon=QIcon(), toolTip=""): # type: (QWidget, str, QIcon, str) -> QAbstractButton """ Create the tab button for `widget`. """ action = QAction(text, self) action.setCheckable(True) if icon: action.setIcon(icon) if toolTip: action.setToolTip(toolTip) self.__tabActionGroup.addAction(action) self.__actionMapper.setMapping(action, action) action.toggled.connect(self.__actionMapper.map) button = ToolBoxTabButton(self, objectName="toolbox-tab-button") button.setDefaultAction(action) button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) button.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) if self.__tabIconSize.isValid(): button.setIconSize(self.__tabIconSize) if self.__tabButtonHeight > 0: button.setFixedHeight(self.__tabButtonHeight) return button def ensureWidgetVisible(self, child, xmargin=50, ymargin=50): # type: (QWidget, int, int) -> None """ Scroll the contents so child widget instance is visible inside the viewport. """ self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin) def sizeHint(self): # type: () -> QSize """ Reimplemented. """ hint = self.__contentsLayout.sizeHint() if self.count(): # Compute max width of hidden widgets also. scroll = self.__scrollArea # check if scrollbar is transient scrollBar = self.__scrollArea.verticalScrollBar() transient = scrollBar.style().styleHint( QStyle.SH_ScrollBar_Transient, widget=scrollBar) scroll_w = scroll.verticalScrollBar().sizeHint().width( ) if not transient else 0 frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2 max_w = max([p.widget.sizeHint().width() for p in self.__pages]) hint = QSize( max(max_w, hint.width()) + scroll_w + frame_w, hint.height()) return QSize(200, 200).expandedTo(hint) def __onTabActionToggled(self, action): # type: (QAction) -> None page = find(self.__pages, action, key=attrgetter("action")) on = action.isChecked() page.widget.setVisible(on) index = page.index if index > 0: # Update the `previous` tab buttons style hints previous = self.__pages[index - 1].button previous.selected = set_flag(previous.selected, ToolBoxTabButton.NextIsSelected, on) previous.update() if index < self.count() - 1: next = self.__pages[index + 1].button next.selected = set_flag(next.selected, ToolBoxTabButton.PreviousIsSelected, on) next.update() self.tabToggled.emit(index, on) self.__contentsLayout.invalidate() def __updateSelected(self): # type: () -> None """Update the tab buttons selected style flags. """ if self.count() == 0: return def update(button, next_sel, prev_sel): # type: (ToolBoxTabButton, bool, bool) -> None button.selected = set_flag(button.selected, ToolBoxTabButton.NextIsSelected, next_sel) button.selected = set_flag(button.selected, ToolBoxTabButton.PreviousIsSelected, prev_sel) button.update() if self.count() == 1: update(self.__pages[0].button, False, False) elif self.count() >= 2: pages = self.__pages for i in range(1, self.count() - 1): update(pages[i].button, pages[i + 1].action.isChecked(), pages[i - 1].action.isChecked()) def __updatePositions(self): # type: () -> None """Update the tab buttons position style flags. """ if self.count() == 0: return elif self.count() == 1: self.__pages[0].button.position = ToolBoxTabButton.OnlyOneTab else: self.__pages[0].button.position = ToolBoxTabButton.Beginning self.__pages[-1].button.position = ToolBoxTabButton.End for p in self.__pages[1:-1]: p.button.position = ToolBoxTabButton.Middle for p in self.__pages: p.button.update()