class DiscreteFeatureEditor(FeatureEditor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.valuesedit = QLineEdit() self.valuesedit.textChanged.connect(self._invalidate) layout = self.layout() layout.addRow(self.tr("Values"), self.valuesedit) def setEditorData(self, data, domain): self.valuesedit.setText( ", ".join(v.replace(",", r"\,") for v in data.values)) super().setEditorData(data, domain) def editorData(self): values = self.valuesedit.text() values = re.split(r"(?<!\\),", values) values = tuple(filter(None, [v.replace(r"\,", ",").strip() for v in values])) return DiscreteDescriptor( name=self.nameedit.text(), values=values, base_value=-1, ordered=False, expression=self.expressionedit.text() )
def __init__(self, tree, dataset, master, parent=None): QLineEdit.__init__(self, parent) Control.__init__(self, tree, dataset, master) if hasattr(tree, "regexp"): self.setValidator(QRegExpValidator(QRegExp(tree.regexp), self)) if hasattr(tree, "defaultValue"): self.setText(tree.defaultValue)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) layout = QFormLayout( fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow ) layout.setContentsMargins(0, 0, 0, 0) self.nameedit = QLineEdit( placeholderText="Name...", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) ) self.expressionedit = QLineEdit( placeholderText="Expression..." ) self.attrs_model = itemmodels.VariableListModel( ["Select Feature"], parent=self) self.attributescb = QComboBox( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) ) self.attributescb.setModel(self.attrs_model) sorted_funcs = sorted(self.FUNCTIONS) self.funcs_model = itemmodels.PyListModelTooltip() self.funcs_model.setParent(self) self.funcs_model[:] = chain(["Select Function"], sorted_funcs) self.funcs_model.tooltips[:] = chain( [''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs]) self.functionscb = QComboBox( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.functionscb.setModel(self.funcs_model) hbox = QHBoxLayout() hbox.addWidget(self.attributescb) hbox.addWidget(self.functionscb) layout.addRow(self.nameedit, self.expressionedit) layout.addRow(self.tr(""), hbox) self.setLayout(layout) self.nameedit.editingFinished.connect(self._invalidate) self.expressionedit.textChanged.connect(self._invalidate) self.attributescb.currentIndexChanged.connect(self.on_attrs_changed) self.functionscb.currentIndexChanged.connect(self.on_funcs_changed) self._modified = False
def __run_add_package_dialog(self): self.__add_package_by_name_dialog = dlg = QDialog( self, windowTitle="Add add-on by name", ) dlg.setAttribute(Qt.WA_DeleteOnClose) vlayout = QVBoxLayout() form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) nameentry = QLineEdit( placeholderText="Package name", toolTip="Enter a package name as displayed on " "PyPI (capitalization is not important)") nameentry.setMinimumWidth(250) form.addRow("Name:", nameentry) vlayout.addLayout(form) buttons = QDialogButtonBox( standardButtons=QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) okb = buttons.button(QDialogButtonBox.Ok) okb.setEnabled(False) okb.setText("Add") def changed(name): okb.setEnabled(bool(name)) nameentry.textChanged.connect(changed) vlayout.addWidget(buttons) vlayout.setSizeConstraint(QVBoxLayout.SetFixedSize) dlg.setLayout(vlayout) f = None def query(): nonlocal f name = nameentry.text() def query_pypi(name): # type: (str) -> _QueryResult res = pypi_json_query_project_meta([name]) assert len(res) == 1 r = res[0] if r is not None: r = installable_from_json_response(r) return _QueryResult(queryname=name, installable=r) f = self.__executor.submit(query_pypi, name) okb.setDisabled(True) f.add_done_callback( method_queued(self.__on_add_single_query_finish, (object,)) ) buttons.accepted.connect(query) buttons.rejected.connect(dlg.reject) dlg.exec_()
def removeActionAt(self, position): """ Remove the action at position. """ self._checkPosition(position) slot = self.__actions[position - 1] self.__actions[position - 1] = None slot.button.hide() slot.button.deleteLater() QLineEdit.removeAction(self, slot.action) self.__layoutActions()
def __init__(self, parent): QFrame.__init__(self, parent) self.setContentsMargins(0, 0, 0, 0) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(1) self._setNameLineEdit = QLineEdit(self) layout.addWidget(self._setNameLineEdit) self._setListView = QListView(self) self._listModel = QStandardItemModel(self) self._proxyModel = QSortFilterProxyModel(self) self._proxyModel.setSourceModel(self._listModel) self._setListView.setModel(self._proxyModel) self._setListView.setItemDelegate(ListItemDelegate(self)) self._setNameLineEdit.textChanged.connect( self._proxyModel.setFilterFixedString) self._completer = QCompleter(self._listModel, self) self._setNameLineEdit.setCompleter(self._completer) self._listModel.itemChanged.connect(self._onSetNameChange) layout.addWidget(self._setListView) buttonLayout = QHBoxLayout() self._addAction = QAction( "+", self, toolTip="Add a new sort key") self._updateAction = QAction( "Update", self, toolTip="Update/save current selection") self._removeAction = QAction( "\u2212", self, toolTip="Remove selected sort key.") self._addToolButton = QToolButton(self) self._updateToolButton = QToolButton(self) self._removeToolButton = QToolButton(self) self._updateToolButton.setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._addToolButton.setDefaultAction(self._addAction) self._updateToolButton.setDefaultAction(self._updateAction) self._removeToolButton.setDefaultAction(self._removeAction) buttonLayout.addWidget(self._addToolButton) buttonLayout.addWidget(self._updateToolButton) buttonLayout.addWidget(self._removeToolButton) layout.addLayout(buttonLayout) self.setLayout(layout) self._addAction.triggered.connect(self.addCurrentSelection) self._updateAction.triggered.connect(self.updateSelectedSelection) self._removeAction.triggered.connect(self.removeSelectedSelection) self._setListView.selectionModel().selectionChanged.connect( self._onListViewSelectionChanged) self.selectionModel = None self._selections = []
def paintEvent(self, event): QLineEdit.paintEvent(self, event) if not self.text() and self.placeholderText() and \ not self.hasFocus(): p = QStylePainter(self) font = self.font() metrics = QFontMetrics(font) p.setFont(font) color = self.palette().color(QPalette.Mid) p.setPen(color) left, top, right, bottom = self.getTextMargins() contents = self.contentsRect() contents = contents.adjusted(left, top, -right, -bottom) text = metrics.elidedText(self.placeholderText(), Qt.ElideMiddle, contents.width()) p.drawText(contents, Qt.AlignLeft | Qt.AlignVCenter, text)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.valuesedit = QLineEdit() self.valuesedit.textChanged.connect(self._invalidate) layout = self.layout() layout.addRow(self.tr("Values"), self.valuesedit)
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__searchline = QLineEdit(self, visible=False, frame=False) self.__searchline.setAttribute(Qt.WA_MacShowFocusRect, False) self.__searchline.setFocusProxy(self) self.__popup = None # type: Optional[QAbstractItemModel] self.__proxy = None # type: Optional[QSortFilterProxyModel] self.__popupTimer = QElapsedTimer() self.setFocusPolicy(Qt.ClickFocus | Qt.TabFocus)
def __init__(self, master): """Initialize the attributes and set up the interface""" QDialog.__init__(self, master, windowTitle=self.captionTitle) WidgetMessagesMixin.__init__(self) self.setLayout(QVBoxLayout()) self.insert_message_bar() self.layout().insertWidget(0, self.message_bar) self.master = master self.keep_running = False self.scheduled_call = None self.saved_state = None self.saved_progress = 0 self.scores = [] self.add_to_model = queue.Queue() self.update_timer = QTimer(self) self.update_timer.timeout.connect(self._update) self.update_timer.setInterval(200) self._thread = None self._worker = None self.filter = QLineEdit() self.filter.setPlaceholderText("Filter ...") self.filter.textChanged.connect(self.filter_changed) self.layout().addWidget(self.filter) # Remove focus from line edit self.setFocus(Qt.ActiveWindowFocusReason) self.rank_model = QStandardItemModel(self) self.model_proxy = QSortFilterProxyModel( self, filterCaseSensitivity=False) self.model_proxy.setSourceModel(self.rank_model) self.rank_table = view = QTableView( selectionBehavior=QTableView.SelectRows, selectionMode=QTableView.SingleSelection, showGrid=False, editTriggers=gui.TableView.NoEditTriggers) if self._has_bars: view.setItemDelegate(TableBarItem()) else: view.setItemDelegate(HorizontalGridDelegate()) view.setModel(self.model_proxy) view.selectionModel().selectionChanged.connect( self.on_selection_changed) view.horizontalHeader().setStretchLastSection(True) view.horizontalHeader().hide() self.layout().addWidget(view) self.button = gui.button( self, self, "Start", callback=self.toggle, default=True)
def setAction(self, action, position=LeftPosition): """ Set `action` to be displayed at `position`. Existing action (if present) will be removed. Parameters ---------- action : :class:`QAction` position : int Position where to set the action (default: ``LeftPosition``). """ curr = self.actionAt(position) if curr is not None: self.removeAction(position) # Add the action using QWidget.addAction (for shortcuts) QLineEdit.addAction(self, action) button = LineEditButton(self) button.setToolButtonStyle(Qt.ToolButtonIconOnly) button.setDefaultAction(action) button.setVisible(self.isVisible()) button.show() button.setCursor(Qt.ArrowCursor) button.triggered.connect(self.triggered) button.triggered.connect(self.__onTriggered) slot = _ActionSlot(position, action, button, False) self.__actions[position - 1] = slot if not self.testAttribute(Qt.WA_Resized): # Need some sensible height to do the layout. self.adjustSize() self.__layoutActions()
def __key_pressed(self, event): QLineEdit.keyPressEvent(self.form.lineEdit, event) self.key_pressed_event(event)
class OWKeywords(OWWidget, ConcurrentWidgetMixin): name = "Extract Keywords" description = "Infers characteristic words from the input corpus." icon = "icons/Keywords.svg" priority = 1100 keywords = ["characteristic", "term"] buttons_area_orientation = Qt.Vertical DEFAULT_SORTING = (1, Qt.DescendingOrder) settingsHandler = DomainContextHandler() selected_scoring_methods: Set[str] = Setting({ScoringMethods.TF_IDF}) yake_lang_index: int = Setting(YAKE_LANGUAGES.index("English")) rake_lang_index: int = Setting(RAKE_LANGUAGES.index("English")) embedding_lang_index: int = Setting(EMBEDDING_LANGUAGES.index("English")) agg_method: int = Setting(AggregationMethods.MEAN) sel_method: int = ContextSetting(SelectionMethods.N_BEST) n_selected: int = ContextSetting(3) sort_column_order: Tuple[int, int] = Setting(DEFAULT_SORTING) selected_words = ContextSetting([], schema_only=True) auto_apply: bool = Setting(True) class Inputs: corpus = Input("Corpus", Corpus, default=True) words = Input("Words", Table) class Outputs: words = Output("Words", Table, dynamic=False) class Warning(OWWidget.Warning): no_words_column = Msg("Input is missing 'Words' column.") def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self.corpus: Optional[Corpus] = None self.words: Optional[List] = None self.__cached_keywords = {} self.model = KeywordsTableModel(parent=self) self._setup_gui() def _setup_gui(self): grid = QGridLayout() box = gui.widgetBox(self.controlArea, "Scoring Methods", grid) yake_cb = gui.comboBox( self.controlArea, self, "yake_lang_index", items=YAKE_LANGUAGES, callback=self.__on_yake_lang_changed ) rake_cb = gui.comboBox( self.controlArea, self, "rake_lang_index", items=RAKE_LANGUAGES, callback=self.__on_rake_lang_changed ) embedding_cb = gui.comboBox( self.controlArea, self, "embedding_lang_index", items=EMBEDDING_LANGUAGES, callback=self.__on_emb_lang_changed ) for i, (method_name, _) in enumerate(ScoringMethods.ITEMS): check_box = QCheckBox(method_name, self) check_box.setChecked(method_name in self.selected_scoring_methods) check_box.stateChanged.connect( lambda state, name=method_name: self.__on_scoring_method_state_changed(state, name) ) box.layout().addWidget(check_box, i, 0) if method_name == ScoringMethods.YAKE: box.layout().addWidget(yake_cb, i, 1) if method_name == ScoringMethods.RAKE: box.layout().addWidget(rake_cb, i, 1) if method_name == ScoringMethods.EMBEDDING: box.layout().addWidget(embedding_cb, i, 1) box = gui.vBox(self.controlArea, "Aggregation") gui.comboBox( box, self, "agg_method", items=AggregationMethods.ITEMS, callback=self.update_scores ) box = gui.vBox(self.buttonsArea, "Select Words") grid = QGridLayout() grid.setContentsMargins(0, 0, 0, 0) box.layout().addLayout(grid) self.__sel_method_buttons = QButtonGroup() for method, label in enumerate(SelectionMethods.ITEMS): button = QRadioButton(label) button.setChecked(method == self.sel_method) grid.addWidget(button, method, 0) self.__sel_method_buttons.addButton(button, method) self.__sel_method_buttons.buttonClicked[int].connect( self._set_selection_method ) spin = gui.spin( box, self, "n_selected", 1, 999, addToLayout=False, callback=lambda: self._set_selection_method( SelectionMethods.N_BEST) ) grid.addWidget(spin, 3, 1) gui.rubber(self.controlArea) gui.auto_send(self.buttonsArea, self, "auto_apply") self.__filter_line_edit = QLineEdit( textChanged=self.__on_filter_changed, placeholderText="Filter..." ) self.mainArea.layout().addWidget(self.__filter_line_edit) def select_manual(): self._set_selection_method(SelectionMethods.MANUAL) self.view = KeywordsTableView() self.view.pressedAny.connect(select_manual) self.view.horizontalHeader().setSortIndicator(*self.DEFAULT_SORTING) self.view.horizontalHeader().sectionClicked.connect( self.__on_horizontal_header_clicked) self.mainArea.layout().addWidget(self.view) proxy = SortFilterProxyModel() proxy.setFilterKeyColumn(0) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) self.view.model().setSourceModel(self.model) self.view.selectionModel().selectionChanged.connect( self.__on_selection_changed ) def __on_scoring_method_state_changed(self, state: int, method_name: str): if state == Qt.Checked: self.selected_scoring_methods.add(method_name) elif method_name in self.selected_scoring_methods: self.selected_scoring_methods.remove(method_name) self.update_scores() def __on_yake_lang_changed(self): if ScoringMethods.YAKE in self.selected_scoring_methods: if ScoringMethods.YAKE in self.__cached_keywords: del self.__cached_keywords[ScoringMethods.YAKE] self.update_scores() def __on_rake_lang_changed(self): if ScoringMethods.RAKE in self.selected_scoring_methods: if ScoringMethods.RAKE in self.__cached_keywords: del self.__cached_keywords[ScoringMethods.RAKE] self.update_scores() def __on_emb_lang_changed(self): if ScoringMethods.EMBEDDING in self.selected_scoring_methods: if ScoringMethods.EMBEDDING in self.__cached_keywords: del self.__cached_keywords[ScoringMethods.EMBEDDING] self.update_scores() def __on_filter_changed(self): model = self.view.model() model.setFilterFixedString(self.__filter_line_edit.text().strip()) self._select_rows() def __on_horizontal_header_clicked(self, index: int): header = self.view.horizontalHeader() self.sort_column_order = (index, header.sortIndicatorOrder()) self._select_rows() # explicitly call commit, because __on_selection_changed will not be # invoked, since selection is actually the same, only order is not if self.sel_method == SelectionMethods.MANUAL and self.selected_words \ or self.sel_method == SelectionMethods.ALL: self.commit.deferred() def __on_selection_changed(self): selected_rows = self.view.selectionModel().selectedRows(0) model = self.view.model() self.selected_words = [model.data(model.index(i.row(), 0)) for i in selected_rows] self.commit.deferred() @Inputs.corpus def set_corpus(self, corpus: Optional[Corpus]): self.closeContext() self._clear() self.corpus = corpus self.openContext(self.corpus) self.__sel_method_buttons.button(self.sel_method).setChecked(True) def _clear(self): self.clear_messages() self.cancel() self.selected_words = [] self.model.clear() self.__cached_keywords = {} @Inputs.words def set_words(self, words: Optional[Table]): self.words = None self.Warning.no_words_column.clear() if words: if WORDS_COLUMN_NAME in words.domain and words.domain[ WORDS_COLUMN_NAME].attributes.get("type") == "words": self.words = list(words.get_column_view(WORDS_COLUMN_NAME)[0]) else: self.Warning.no_words_column() def handleNewSignals(self): self.update_scores() def update_scores(self): kwargs = { ScoringMethods.YAKE: { "language": YAKE_LANGUAGES[self.yake_lang_index], "max_len": self.corpus.ngram_range[1] if self.corpus else 1 }, ScoringMethods.RAKE: { "language": RAKE_LANGUAGES[self.rake_lang_index], "max_len": self.corpus.ngram_range[1] if self.corpus else 1 }, ScoringMethods.EMBEDDING: { "language": EMBEDDING_LANGUAGES[self.embedding_lang_index], }, } self.start(run, self.corpus, self.words, self.__cached_keywords, self.selected_scoring_methods, kwargs, self.agg_method) def _set_selection_method(self, method: int): self.sel_method = method self.__sel_method_buttons.button(method).setChecked(True) self._select_rows() def _select_rows(self): model = self.view.model() n_rows, n_columns = model.rowCount(), model.columnCount() if self.sel_method == SelectionMethods.NONE: selection = QItemSelection() elif self.sel_method == SelectionMethods.ALL: selection = QItemSelection( model.index(0, 0), model.index(n_rows - 1, n_columns - 1) ) elif self.sel_method == SelectionMethods.MANUAL: selection = QItemSelection() for i in range(n_rows): word = model.data(model.index(i, 0)) if word in self.selected_words: _selection = QItemSelection(model.index(i, 0), model.index(i, n_columns - 1)) selection.merge(_selection, QItemSelectionModel.Select) elif self.sel_method == SelectionMethods.N_BEST: n_sel = min(self.n_selected, n_rows) selection = QItemSelection( model.index(0, 0), model.index(n_sel - 1, n_columns - 1) ) else: raise NotImplementedError self.view.selectionModel().select( selection, QItemSelectionModel.ClearAndSelect ) def on_exception(self, ex: Exception): raise ex def on_partial_result(self, _: Any): pass # pylint: disable=arguments-differ def on_done(self, results: Results): self.__cached_keywords = results.all_keywords self.model.wrap(results.scores) self.model.setHorizontalHeaderLabels(["Word"] + results.labels) self._apply_sorting() if self.model.rowCount() > 0: self._select_rows() else: self.__on_selection_changed() def _apply_sorting(self): if self.model.columnCount() <= self.sort_column_order[0]: self.sort_column_order = self.DEFAULT_SORTING header = self.view.horizontalHeader() current_sorting = (header.sortIndicatorSection(), header.sortIndicatorOrder()) if current_sorting != self.sort_column_order: header.setSortIndicator(*self.sort_column_order) def onDeleteWidget(self): self.shutdown() super().onDeleteWidget() @gui.deferred def commit(self): words = None if self.selected_words: sort_column, reverse = self.sort_column_order model = self.model attrs = [ContinuousVariable(model.headerData(i, Qt.Horizontal)) for i in range(1, model.columnCount())] data = sorted(model, key=lambda a: a[sort_column], reverse=reverse) words_data = [s[0] for s in data if s[0] in self.selected_words] words = create_words_table(words_data) words = words.transform(Domain(attrs, metas=words.domain.metas)) with words.unlocked(words.X): for i in range(len(attrs)): words.X[:, i] = [data[j][i + 1] for j in range(len(data)) if data[j][0] in self.selected_words] self.Outputs.words.send(words) def send_report(self): if not self.corpus: return self.report_data("Corpus", self.corpus) if self.words is not None: self.report_paragraph("Words", ", ".join(self.words)) self.report_table("Keywords", self.view, num_format="{:.3f}")
class OWSql(OWWidget): name = "SQL Table" id = "orange.widgets.data.sql" description = "Load data set from SQL." icon = "icons/SQLTable.svg" priority = 30 category = "Data" keywords = ["data", "file", "load", "read", "SQL"] class Outputs: data = Output("Data", Table, doc="Attribute-valued data set read from the input file.") settings_version = 2 want_main_area = False resizing_enabled = False host = Setting(None) port = Setting(None) database = Setting(None) schema = Setting(None) username = "" password = "" table = Setting(None) sql = Setting("") guess_values = Setting(True) download = Setting(False) materialize = Setting(False) materialize_table_name = Setting("") class Information(OWWidget.Information): data_sampled = Msg("Data description was generated from a sample.") class Error(OWWidget.Error): connection = Msg("{}") no_backends = Msg("Please install a backend to use this widget") missing_extension = Msg("Database is missing extension{}: {}") def __init__(self): super().__init__() self.backend = None self.data_desc_table = None self.database_desc = None vbox = gui.vBox(self.controlArea, "Server", addSpace=True) box = gui.vBox(vbox) self.backends = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) if len(self.backends): self.backendcombo.setModel(self.backends) else: self.Error.no_backends() box.setEnabled(False) box.layout().addWidget(self.backendcombo) self.servertext = QLineEdit(box) self.servertext.setPlaceholderText('Server') self.servertext.setToolTip('Server') self.servertext.editingFinished.connect(self._load_credentials) if self.host: self.servertext.setText(self.host if not self.port else '{}:{}'.format(self.host, self.port)) box.layout().addWidget(self.servertext) self.databasetext = QLineEdit(box) self.databasetext.setPlaceholderText('Database[/Schema]') self.databasetext.setToolTip('Database or optionally Database/Schema') if self.database: self.databasetext.setText( self.database if not self.schema else '{}/{}'.format(self.database, self.schema)) box.layout().addWidget(self.databasetext) self.usernametext = QLineEdit(box) self.usernametext.setPlaceholderText('Username') self.usernametext.setToolTip('Username') box.layout().addWidget(self.usernametext) self.passwordtext = QLineEdit(box) self.passwordtext.setPlaceholderText('Password') self.passwordtext.setToolTip('Password') self.passwordtext.setEchoMode(QLineEdit.Password) box.layout().addWidget(self.passwordtext) self._load_credentials() self.tables = TableModel() tables = gui.hBox(box) self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength ) self.tablecombo.setModel(self.tables) self.tablecombo.setToolTip('table') tables.layout().addWidget(self.tablecombo) self.connect() index = self.tablecombo.findText(str(self.table)) if index != -1: self.tablecombo.setCurrentIndex(index) # set up the callback to select_table in case of selection change self.tablecombo.activated[int].connect(self.select_table) self.connectbutton = gui.button( tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) tables.layout().addWidget(self.connectbutton) self.custom_sql = gui.vBox(box) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) self.custom_sql.layout().addWidget(self.sqltext) mt = gui.hBox(self.custom_sql) cb = gui.checkBox(mt, self, 'materialize', 'Materialize to table ') cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') self.executebtn = gui.button( self.custom_sql, self, 'Execute', callback=self.open_table) box.layout().addWidget(self.custom_sql) gui.checkBox(box, self, "guess_values", "Auto-discover categorical variables", callback=self.open_table) gui.checkBox(box, self, "download", "Download data to local memory", callback=self.open_table) gui.rubber(self.buttonsArea) QTimer.singleShot(0, self.select_table) def _load_credentials(self): self._parse_host_port() cm = self._credential_manager(self.host, self.port) self.username = cm.username self.password = cm.password if self.username: self.usernametext.setText(self.username) if self.password: self.passwordtext.setText(self.password) def _save_credentials(self): cm = self._credential_manager(self.host, self.port) cm.username = self.username or '' cm.password = self.password or '' def _credential_manager(self, host, port): return CredentialManager("SQL Table: {}:{}".format(host, port)) def error(self, id=0, text=""): super().error(id, text) err_style = 'QLineEdit {border: 2px solid red;}' if 'server' in text or 'host' in text: self.servertext.setStyleSheet(err_style) else: self.servertext.setStyleSheet('') if 'role' in text: self.usernametext.setStyleSheet(err_style) else: self.usernametext.setStyleSheet('') if 'database' in text: self.databasetext.setStyleSheet(err_style) else: self.databasetext.setStyleSheet('') def _parse_host_port(self): hostport = self.servertext.text().split(':') self.host = hostport[0] self.port = hostport[1] if len(hostport) == 2 else None def connect(self): self._parse_host_port() self.database, _, self.schema = self.databasetext.text().partition('/') self.username = self.usernametext.text() or None self.password = self.passwordtext.text() or None try: if self.backendcombo.currentIndex() < 0: return backend = self.backends[self.backendcombo.currentIndex()] self.backend = backend(dict( host=self.host, port=self.port, database=self.database, user=self.username, password=self.password )) self.Error.connection.clear() self._save_credentials() self.database_desc = OrderedDict(( ("Host", self.host), ("Port", self.port), ("Database", self.database), ("User name", self.username) )) self.refresh_tables() except BackendError as err: error = str(err).split('\n')[0] self.Error.connection(error) self.database_desc = self.data_desc_table = None self.tablecombo.clear() def refresh_tables(self): self.tables.clear() self.Error.missing_extension.clear() if self.backend is None: self.data_desc_table = None return self.tables.append("Select a table") self.tables.append("Custom SQL") self.tables.extend(self.backend.list_tables(self.schema)) # Called on tablecombo selection change: def select_table(self): curIdx = self.tablecombo.currentIndex() if self.tablecombo.itemText(curIdx) != "Custom SQL": self.custom_sql.setVisible(False) return self.open_table() else: self.custom_sql.setVisible(True) self.data_desc_table = None self.database_desc["Table"] = "(None)" self.table = None if len(str(self.sql)) > 14: return self.open_table() #self.Error.missing_extension( # 's' if len(missing) > 1 else '', # ', '.join(missing), # shown=missing) def open_table(self): table = self.get_table() self.data_desc_table = table self.Outputs.data.send(table) def get_table(self): curIdx = self.tablecombo.currentIndex() if curIdx <= 0: if self.database_desc: self.database_desc["Table"] = "(None)" self.data_desc_table = None return if self.tablecombo.itemText(curIdx) != "Custom SQL": self.table = self.tables[self.tablecombo.currentIndex()] self.database_desc["Table"] = self.table if "Query" in self.database_desc: del self.database_desc["Query"] what = self.table else: what = self.sql = self.sqltext.toPlainText() self.table = "Custom SQL" if self.materialize: import psycopg2 if not self.materialize_table_name: self.Error.connection( "Specify a table name to materialize the query") return try: with self.backend.execute_sql_query("DROP TABLE IF EXISTS " + self.materialize_table_name): pass with self.backend.execute_sql_query("CREATE TABLE " + self.materialize_table_name + " AS " + self.sql): pass with self.backend.execute_sql_query("ANALYZE " + self.materialize_table_name): pass except (psycopg2.ProgrammingError, BackendError) as ex: self.Error.connection(str(ex)) return try: table = SqlTable(dict(host=self.host, port=self.port, database=self.database, user=self.username, password=self.password), what, backend=type(self.backend), inspect_values=False) except BackendError as ex: self.Error.connection(str(ex)) return self.Error.connection.clear() sample = False if table.approx_len() > LARGE_TABLE and self.guess_values: confirm = QMessageBox(self) confirm.setIcon(QMessageBox.Warning) confirm.setText("Attribute discovery might take " "a long time on large tables.\n" "Do you want to auto discover attributes?") confirm.addButton("Yes", QMessageBox.YesRole) no_button = confirm.addButton("No", QMessageBox.NoRole) sample_button = confirm.addButton("Yes, on a sample", QMessageBox.YesRole) confirm.exec() if confirm.clickedButton() == no_button: self.guess_values = False elif confirm.clickedButton() == sample_button: sample = True self.Information.clear() if self.guess_values: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) if sample: s = table.sample_time(1) domain = s.get_domain(inspect_values=True) self.Information.data_sampled() else: domain = table.get_domain(inspect_values=True) QApplication.restoreOverrideCursor() table.domain = domain if self.download: if table.approx_len() > MAX_DL_LIMIT: QMessageBox.warning( self, 'Warning', "Data is too big to download.\n" "Consider using the Data Sampler widget to download " "a sample instead.") self.download = False elif table.approx_len() > AUTO_DL_LIMIT: confirm = QMessageBox.question( self, 'Question', "Data appears to be big. Do you really " "want to download it to local memory?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if confirm == QMessageBox.No: self.download = False if self.download: table.download_data(MAX_DL_LIMIT) table = Table(table) return table def send_report(self): if not self.database_desc: self.report_paragraph("No database connection.") return self.report_items("Database", self.database_desc) if self.data_desc_table: self.report_items("Data", report.describe_data(self.data_desc_table)) @classmethod def migrate_settings(cls, settings, version): if version < 2: # Until Orange version 3.4.4 username and password had been stored # in Settings. cm = cls._credential_manager(settings["host"], settings["port"]) cm.username = settings["username"] cm.password = settings["password"]
def get_control_by_type(param): if isinstance(param, tuple): options = param[1:] param = param[0] else: options = () if param == 'str': result = QLineEdit() if 'pwd' in options: result.setEchoMode(QLineEdit.Password) if 'optional' in options: result.setPlaceholderText('(Optional)') elif param == 'big_str': result = QPlainTextEdit() elif param == 'label': result = QLabel() if 'url' in options: result.setTextFormat(Qt.RichText) result.setTextInteractionFlags(Qt.TextBrowserInteraction) result.setOpenExternalLinks(True) elif param == 'checkbox': result = QCheckBox() else: raise RuntimeError() return result
class OWSelectAttributes(widget.OWWidget): name = "Select Columns" description = "Select columns from the data table and assign them to " \ "data features, classes or meta variables." icon = "icons/SelectColumns.svg" priority = 100 inputs = [("Data", Table, "set_data")] outputs = [("Data", Table), ("Features", widget.AttributeList)] want_main_area = False want_control_area = True settingsHandler = SelectAttributesDomainContextHandler() domain_role_hints = ContextSetting({}) auto_commit = Setting(False) def __init__(self): super().__init__() self.controlArea = QWidget(self.controlArea) self.layout().addWidget(self.controlArea) layout = QGridLayout() self.controlArea.setLayout(layout) layout.setContentsMargins(4, 4, 4, 4) box = gui.vBox(self.controlArea, "Available Variables", addToLayout=False) self.filter_edit = QLineEdit() self.filter_edit.setToolTip("Filter the list of available variables.") box.layout().addWidget(self.filter_edit) if hasattr(self.filter_edit, "setPlaceholderText"): self.filter_edit.setPlaceholderText("Filter") self.completer = QCompleter() self.completer.setCompletionMode(QCompleter.InlineCompletion) self.completer_model = QStringListModel() self.completer.setModel(self.completer_model) self.completer.setModelSorting( QCompleter.CaseSensitivelySortedModel) self.filter_edit.setCompleter(self.completer) self.completer_navigator = CompleterNavigator(self) self.filter_edit.installEventFilter(self.completer_navigator) def dropcompleted(action): if action == Qt.MoveAction: self.commit() self.available_attrs = VariableListModel(enable_dnd=True) self.available_attrs_proxy = VariableFilterProxyModel() self.available_attrs_proxy.setSourceModel(self.available_attrs) self.available_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.available_attrs_view.setModel(self.available_attrs_proxy) aa = self.available_attrs aa.dataChanged.connect(self.update_completer_model) aa.rowsInserted.connect(self.update_completer_model) aa.rowsRemoved.connect(self.update_completer_model) self.available_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.available_attrs_view)) self.available_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.filter_edit.textChanged.connect(self.update_completer_prefix) self.filter_edit.textChanged.connect( self.available_attrs_proxy.set_filter_string) box.layout().addWidget(self.available_attrs_view) layout.addWidget(box, 0, 0, 3, 1) box = gui.vBox(self.controlArea, "Features", addToLayout=False) self.used_attrs = VariableListModel(enable_dnd=True) self.used_attrs_view = VariablesListItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.used_attrs_view.setModel(self.used_attrs) self.used_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.used_attrs_view)) self.used_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.used_attrs_view) layout.addWidget(box, 0, 2, 1, 1) box = gui.vBox(self.controlArea, "Target Variable", addToLayout=False) self.class_attrs = ClassVarListItemModel(enable_dnd=True) self.class_attrs_view = ClassVariableItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.class_attrs_view.setModel(self.class_attrs) self.class_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.class_attrs_view)) self.class_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.class_attrs_view.setMaximumHeight(24) box.layout().addWidget(self.class_attrs_view) layout.addWidget(box, 1, 2, 1, 1) box = gui.vBox(self.controlArea, "Meta Attributes", addToLayout=False) self.meta_attrs = VariableListModel(enable_dnd=True) self.meta_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.meta_attrs_view.setModel(self.meta_attrs) self.meta_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.meta_attrs_view)) self.meta_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.meta_attrs_view) layout.addWidget(box, 2, 2, 1, 1) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 0, 1, 1, 1) self.up_attr_button = gui.button(bbox, self, "Up", callback=partial(self.move_up, self.used_attrs_view)) self.move_attr_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.used_attrs_view)) self.down_attr_button = gui.button(bbox, self, "Down", callback=partial(self.move_down, self.used_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 1, 1, 1, 1) self.move_class_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.class_attrs_view, exclusive=True)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 2, 1, 1, 1) self.up_meta_button = gui.button(bbox, self, "Up", callback=partial(self.move_up, self.meta_attrs_view)) self.move_meta_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.meta_attrs_view)) self.down_meta_button = gui.button(bbox, self, "Down", callback=partial(self.move_down, self.meta_attrs_view)) autobox = gui.auto_commit(None, self, "auto_commit", "Send") layout.addWidget(autobox, 3, 0, 1, 3) reset = gui.button(None, self, "Reset", callback=self.reset) autobox.layout().insertWidget(0, self.report_button) autobox.layout().insertWidget(1, reset) autobox.layout().insertSpacing(2, 10) reset.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.report_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) layout.setRowStretch(0, 4) layout.setRowStretch(1, 0) layout.setRowStretch(2, 2) layout.setHorizontalSpacing(0) self.controlArea.setLayout(layout) self.data = None self.output_data = None self.original_completer_items = [] self.resize(500, 600) def set_data(self, data=None): self.update_domain_role_hints() self.closeContext() self.data = data if data is not None: self.openContext(data) all_vars = data.domain.variables + data.domain.metas var_sig = lambda attr: (attr.name, vartype(attr)) domain_hints = {var_sig(attr): ("attribute", i) for i, attr in enumerate(data.domain.attributes)} domain_hints.update({var_sig(attr): ("meta", i) for i, attr in enumerate(data.domain.metas)}) if data.domain.class_vars: domain_hints.update( {var_sig(attr): ("class", i) for i, attr in enumerate(data.domain.class_vars)}) # update the hints from context settings domain_hints.update(self.domain_role_hints) attrs_for_role = lambda role: [ (domain_hints[var_sig(attr)][1], attr) for attr in all_vars if domain_hints[var_sig(attr)][0] == role] attributes = [ attr for place, attr in sorted(attrs_for_role("attribute"), key=lambda a: a[0])] classes = [ attr for place, attr in sorted(attrs_for_role("class"), key=lambda a: a[0])] metas = [ attr for place, attr in sorted(attrs_for_role("meta"), key=lambda a: a[0])] available = [ attr for place, attr in sorted(attrs_for_role("available"), key=lambda a: a[0])] self.used_attrs[:] = attributes self.class_attrs[:] = classes self.meta_attrs[:] = metas self.available_attrs[:] = available else: self.used_attrs[:] = [] self.class_attrs[:] = [] self.meta_attrs[:] = [] self.available_attrs[:] = [] self.unconditional_commit() def update_domain_role_hints(self): """ Update the domain hints to be stored in the widgets settings. """ hints_from_model = lambda role, model: [ ((attr.name, vartype(attr)), (role, i)) for i, attr in enumerate(model)] hints = dict(hints_from_model("available", self.available_attrs)) hints.update(hints_from_model("attribute", self.used_attrs)) hints.update(hints_from_model("class", self.class_attrs)) hints.update(hints_from_model("meta", self.meta_attrs)) self.domain_role_hints = hints def selected_rows(self, view): """ Return the selected rows in the view. """ rows = view.selectionModel().selectedRows() model = view.model() if isinstance(model, QSortFilterProxyModel): rows = [model.mapToSource(r) for r in rows] return [r.row() for r in rows] def move_rows(self, view, rows, offset): model = view.model() newrows = [min(max(0, row + offset), len(model) - 1) for row in rows] for row, newrow in sorted(zip(rows, newrows), reverse=offset > 0): model[row], model[newrow] = model[newrow], model[row] selection = QItemSelection() for nrow in newrows: index = model.index(nrow, 0) selection.select(index, index) view.selectionModel().select( selection, QItemSelectionModel.ClearAndSelect) self.commit() def move_up(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, -1) def move_down(self, view): selected = self.selected_rows(view) self.move_rows(view, selected, 1) def move_selected(self, view, exclusive=False): if self.selected_rows(view): self.move_selected_from_to(view, self.available_attrs_view) elif self.selected_rows(self.available_attrs_view): self.move_selected_from_to(self.available_attrs_view, view, exclusive) def move_selected_from_to(self, src, dst, exclusive=False): self.move_from_to(src, dst, self.selected_rows(src), exclusive) def move_from_to(self, src, dst, rows, exclusive=False): src_model = source_model(src) attrs = [src_model[r] for r in rows] if exclusive and len(attrs) != 1: return for s1, s2 in reversed(list(slices(rows))): del src_model[s1:s2] dst_model = source_model(dst) if exclusive and len(dst_model) > 0: src_model.append(dst_model[0]) del dst_model[0] dst_model.extend(attrs) self.commit() def update_interface_state(self, focus=None, selected=None, deselected=None): for view in [self.available_attrs_view, self.used_attrs_view, self.class_attrs_view, self.meta_attrs_view]: if view is not focus and not view.hasFocus() and self.selected_rows(view): view.selectionModel().clear() def selected_vars(view): model = source_model(view) return [model[i] for i in self.selected_rows(view)] available_selected = selected_vars(self.available_attrs_view) attrs_selected = selected_vars(self.used_attrs_view) class_selected = selected_vars(self.class_attrs_view) meta_selected = selected_vars(self.meta_attrs_view) available_types = set(map(type, available_selected)) all_primitive = all(var.is_primitive() for var in available_types) move_attr_enabled = (available_selected and all_primitive) or \ attrs_selected self.move_attr_button.setEnabled(bool(move_attr_enabled)) if move_attr_enabled: self.move_attr_button.setText(">" if available_selected else "<") move_class_enabled = (len(available_selected) == 1 and all_primitive) or \ class_selected self.move_class_button.setEnabled(bool(move_class_enabled)) if move_class_enabled: self.move_class_button.setText(">" if available_selected else "<") move_meta_enabled = available_selected or meta_selected self.move_meta_button.setEnabled(bool(move_meta_enabled)) if move_meta_enabled: self.move_meta_button.setText(">" if available_selected else "<") def update_completer_model(self, *_): """ This gets called when the model for available attributes changes through either drag/drop or the left/right button actions. """ vars = list(self.available_attrs) items = [var.name for var in vars] items += ["%s=%s" % item for v in vars for item in v.attributes.items()] new = sorted(set(items)) if new != self.original_completer_items: self.original_completer_items = new self.completer_model.setStringList(self.original_completer_items) def update_completer_prefix(self, filter): """ Prefixes all items in the completer model with the current already done completion to enable the completion of multiple keywords. """ prefix = str(self.completer.completionPrefix()) if not prefix.endswith(" ") and " " in prefix: prefix, _ = prefix.rsplit(" ", 1) items = [prefix + " " + item for item in self.original_completer_items] else: items = self.original_completer_items old = list(map(str, self.completer_model.stringList())) if set(old) != set(items): self.completer_model.setStringList(items) def commit(self): self.update_domain_role_hints() if self.data is not None: attributes = list(self.used_attrs) class_var = list(self.class_attrs) metas = list(self.meta_attrs) domain = Orange.data.Domain(attributes, class_var, metas) newdata = self.data.from_table(domain, self.data) self.output_data = newdata self.send("Data", newdata) self.send("Features", widget.AttributeList(attributes)) else: self.output_data = None self.send("Data", None) self.send("Features", None) def reset(self): if self.data is not None: self.available_attrs[:] = [] self.used_attrs[:] = self.data.domain.attributes self.class_attrs[:] = self.data.domain.class_vars self.meta_attrs[:] = self.data.domain.metas self.update_domain_role_hints() self.commit() def send_report(self): if not self.data or not self.output_data: return in_domain, out_domain = self.data.domain, self.output_data.domain self.report_domain("Input data", self.data.domain) if (in_domain.attributes, in_domain.class_vars, in_domain.metas) == ( out_domain.attributes, out_domain.class_vars, out_domain.metas): self.report_paragraph("Output data", "No changes.") else: self.report_domain("Output data", self.output_data.domain) diff = list(set(in_domain.variables + in_domain.metas) - set(out_domain.variables + out_domain.metas)) if diff: text = "%i (%s)" % (len(diff), ", ".join(x.name for x in diff)) self.report_items((("Removed", text),))
class VariableEditor(QWidget): """ An editor widget for a variable. Can edit the variable name, and its attributes dictionary. """ variable_changed = Signal() def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.var = None # type: Optional[Variable] layout = QVBoxLayout() self.setLayout(layout) self.form = form = QFormLayout( fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, objectName="editor-form-layout" ) layout.addLayout(self.form) self.name_edit = QLineEdit(objectName="name-editor") self.name_edit.editingFinished.connect( lambda: self.name_edit.isModified() and self.on_name_changed() ) form.addRow("Name:", self.name_edit) vlayout = QVBoxLayout(margin=0, spacing=1) self.labels_edit = view = QTreeView( objectName="annotation-pairs-edit", rootIsDecorated=False, editTriggers=QTreeView.DoubleClicked | QTreeView.EditKeyPressed, ) self.labels_model = model = DictItemsModel() view.setModel(model) view.selectionModel().selectionChanged.connect( self.on_label_selection_changed) agrp = QActionGroup(view, objectName="annotate-action-group") action_add = QAction( "+", self, objectName="action-add-label", toolTip="Add a new label.", shortcut=QKeySequence(QKeySequence.New), shortcutContext=Qt.WidgetShortcut ) action_delete = QAction( "\N{MINUS SIGN}", self, objectName="action-delete-label", toolTip="Remove selected label.", shortcut=QKeySequence(QKeySequence.Delete), shortcutContext=Qt.WidgetShortcut ) agrp.addAction(action_add) agrp.addAction(action_delete) view.addActions([action_add, action_delete]) def add_label(): row = [QStandardItem(), QStandardItem()] model.appendRow(row) idx = model.index(model.rowCount() - 1, 0) view.setCurrentIndex(idx) view.edit(idx) def remove_label(): rows = view.selectionModel().selectedRows(0) if rows: assert len(rows) == 1 idx = rows[0].row() model.removeRow(idx) action_add.triggered.connect(add_label) action_delete.triggered.connect(remove_label) agrp.setEnabled(False) self.add_label_action = action_add self.remove_label_action = action_delete # Necessary signals to know when the labels change model.dataChanged.connect(self.on_labels_changed) model.rowsInserted.connect(self.on_labels_changed) model.rowsRemoved.connect(self.on_labels_changed) vlayout.addWidget(self.labels_edit) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) button = FixedSizeButton( self, defaultAction=self.add_label_action, accessibleName="Add", ) hlayout.addWidget(button) button = FixedSizeButton( self, defaultAction=self.remove_label_action, accessibleName="Remove", ) hlayout.addWidget(button) hlayout.addStretch(10) vlayout.addLayout(hlayout) form.addRow("Labels:", vlayout) def set_data(self, var, transform=()): # type: (Optional[Variable], Sequence[Transform]) -> None """ Set the variable to edit. """ self.clear() self.var = var if var is not None: name = var.name annotations = var.annotations for tr in transform: if isinstance(tr, Rename): name = tr.name elif isinstance(tr, Annotate): annotations = tr.annotations self.name_edit.setText(name) self.labels_model.set_dict(dict(annotations)) self.add_label_action.actionGroup().setEnabled(True) else: self.add_label_action.actionGroup().setEnabled(False) def get_data(self): """Retrieve the modified variable. """ if self.var is None: return None, [] name = self.name_edit.text().strip() labels = tuple(sorted(self.labels_model.get_dict().items())) tr = [] if self.var.name != name: tr.append(Rename(name)) if self.var.annotations != labels: tr.append(Annotate(labels)) return self.var, tr def clear(self): """Clear the editor state. """ self.var = None self.name_edit.setText("") self.labels_model.setRowCount(0) @Slot() def on_name_changed(self): self.variable_changed.emit() @Slot() def on_labels_changed(self): self.variable_changed.emit() @Slot() def on_label_selection_changed(self): selected = self.labels_edit.selectionModel().selectedRows() self.remove_label_action.setEnabled(bool(len(selected)))
def __setupUi(self): """Set up the UI. """ if self.__macUnified: self.tab = QToolBar() self.addToolBar(Qt.TopToolBarArea, self.tab) self.setUnifiedTitleAndToolBarOnMac(True) # This does not seem to work self.setWindowFlags(self.windowFlags() & ~Qt.MacWindowToolBarButtonHint) self.tab.actionTriggered[QAction].connect( self.__macOnToolBarAction) central = QStackedWidget() central.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) else: self.tab = central = QTabWidget(self) self.stack = central self.setCentralWidget(central) # General Tab tab = QWidget() self.addTab(tab, self.tr("General"), toolTip=self.tr("General Options")) form = QFormLayout() tab.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) nodes = QWidget(self, objectName="nodes") nodes.setLayout(QVBoxLayout()) nodes.layout().setContentsMargins(0, 0, 0, 0) cb_anim = QCheckBox( self.tr("Enable node animations"), objectName="enable-node-animations", toolTip=self.tr("Enable shadow and ping animations for nodes " "in the workflow."), ) self.bind(cb_anim, "checked", "schemeedit/enable-node-animations") nodes.layout().addWidget(cb_anim) form.addRow(self.tr("Nodes"), nodes) links = QWidget(self, objectName="links") links.setLayout(QVBoxLayout()) links.layout().setContentsMargins(0, 0, 0, 0) cb_show = QCheckBox( self.tr("Show channel names between widgets"), objectName="show-channel-names", toolTip=self.tr("Show source and sink channel names " "over the links."), ) self.bind(cb_show, "checked", "schemeedit/show-channel-names") links.layout().addWidget(cb_show) form.addRow(self.tr("Links"), links) quickmenu = QWidget(self, objectName="quickmenu-options") quickmenu.setLayout(QVBoxLayout()) quickmenu.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox( self.tr("On double click"), toolTip=self.tr("Open quick menu on a double click " "on an empty spot in the canvas"), ) cb2 = QCheckBox( self.tr("On right click"), toolTip=self.tr("Open quick menu on a right click " "on an empty spot in the canvas"), ) cb3 = QCheckBox( self.tr("On space key press"), toolTip=self.tr("On Space key press while the mouse" "is hovering over the canvas."), ) cb4 = QCheckBox( self.tr("On any key press"), toolTip=self.tr("On any key press while the mouse" "is hovering over the canvas."), ) self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") quickmenu.layout().addWidget(cb1) quickmenu.layout().addWidget(cb2) quickmenu.layout().addWidget(cb3) quickmenu.layout().addWidget(cb4) form.addRow(self.tr("Open quick menu on"), quickmenu) startup = QWidget(self, objectName="startup-group") startup.setLayout(QVBoxLayout()) startup.layout().setContentsMargins(0, 0, 0, 0) cb_splash = QCheckBox(self.tr("Show splash screen"), self, objectName="show-splash-screen") cb_welcome = QCheckBox(self.tr("Show welcome screen"), self, objectName="show-welcome-screen") cb_updates = QCheckBox(self.tr("Check for updates"), self, objectName="check-updates") self.bind(cb_splash, "checked", "startup/show-splash-screen") self.bind(cb_welcome, "checked", "startup/show-welcome-screen") self.bind(cb_updates, "checked", "startup/check-updates") startup.layout().addWidget(cb_splash) startup.layout().addWidget(cb_welcome) startup.layout().addWidget(cb_updates) form.addRow(self.tr("On startup"), startup) toolbox = QWidget(self, objectName="toolbox-group") toolbox.setLayout(QVBoxLayout()) toolbox.layout().setContentsMargins(0, 0, 0, 0) exclusive = QCheckBox(self.tr("Only one tab can be open at a time")) self.bind(exclusive, "checked", "mainwindow/toolbox-dock-exclusive") toolbox.layout().addWidget(exclusive) form.addRow(self.tr("Tool box"), toolbox) tab.setLayout(form) # Output Tab tab = QWidget() self.addTab(tab, self.tr("Output"), toolTip="Output Redirection") form = QFormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) combo = QComboBox() combo.addItems([ self.tr("Critical"), self.tr("Error"), self.tr("Warn"), self.tr("Info"), self.tr("Debug"), ]) self.bind(combo, "currentIndex", "logging/level") layout.addWidget(combo) box.setLayout(layout) form.addRow(self.tr("Logging"), box) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox(self.tr("Open in external browser"), objectName="open-in-external-browser") self.bind(cb1, "checked", "help/open-in-external-browser") layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Help window"), box) tab.setLayout(form) # Error Reporting Tab tab = QWidget() self.addTab( tab, self.tr("Error Reporting"), toolTip="Settings related to error reporting", ) form = QFormLayout() line_edit_mid = QLineEdit() self.bind(line_edit_mid, "text", "error-reporting/machine-id") form.addRow("Machine ID:", line_edit_mid) tab.setLayout(form) # Add-ons Tab tab = QWidget() self.addTab(tab, self.tr("Add-ons"), toolTip="Settings related to add-on installation") form = QFormLayout() conda = QWidget(self, objectName="conda-group") conda.setLayout(QVBoxLayout()) conda.layout().setContentsMargins(0, 0, 0, 0) cb_conda_install = QCheckBox(self.tr("Install add-ons with conda"), self, objectName="allow-conda") self.bind(cb_conda_install, "checked", "add-ons/allow-conda") conda.layout().addWidget(cb_conda_install) form.addRow(self.tr("Conda"), conda) form.addRow(self.tr("Pip"), QLabel("Pip install arguments:")) line_edit_pip = QLineEdit() self.bind(line_edit_pip, "text", "add-ons/pip-install-arguments") form.addRow("", line_edit_pip) tab.setLayout(form) # Network Tab tab = QWidget() self.addTab(tab, self.tr("Network"), toolTip="Settings related to networking") form = QFormLayout() line_edit_http_proxy = QLineEdit() self.bind(line_edit_http_proxy, "text", "network/http-proxy") form.addRow("HTTP proxy:", line_edit_http_proxy) line_edit_https_proxy = QLineEdit() self.bind(line_edit_https_proxy, "text", "network/https-proxy") form.addRow("HTTPS proxy:", line_edit_https_proxy) tab.setLayout(form) if self.__macUnified: # Need some sensible size otherwise mac unified toolbar 'takes' # the space that should be used for layout of the contents self.adjustSize()
def __init__(self, parent=None, signalManager=None, name="Databases update"): OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False) self.searchString = "" fbox = gui.widgetBox(self.controlArea, "Filter") self.completer = TokenListCompleter( self, caseSensitivity=Qt.CaseInsensitive) self.lineEditFilter = QLineEdit(textChanged=self.SearchUpdate) self.lineEditFilter.setCompleter(self.completer) fbox.layout().addWidget(self.lineEditFilter) box = gui.widgetBox(self.controlArea, "Files") self.filesView = QTreeWidget(self) self.filesView.setHeaderLabels( ["", "Data Source", "Update", "Last Updated", "Size"]) self.filesView.setRootIsDecorated(False) self.filesView.setUniformRowHeights(True) self.filesView.setSelectionMode(QAbstractItemView.NoSelection) self.filesView.setSortingEnabled(True) self.filesView.sortItems(1, Qt.AscendingOrder) self.filesView.setItemDelegateForColumn( 0, UpdateOptionsItemDelegate(self.filesView)) self.filesView.model().layoutChanged.connect(self.SearchUpdate) box.layout().addWidget(self.filesView) box = gui.widgetBox(self.controlArea, orientation="horizontal") self.updateButton = gui.button( box, self, "Update all", callback=self.UpdateAll, tooltip="Update all updatable files", ) self.downloadButton = gui.button( box, self, "Download all", callback=self.DownloadFiltered, tooltip="Download all filtered files shown" ) self.cancelButton = gui.button( box, self, "Cancel", callback=self.Cancel, tooltip="Cancel scheduled downloads/updates." ) self.retryButton = gui.button( box, self, "Reconnect", callback=self.RetrieveFilesList ) self.retryButton.hide() gui.rubber(box) self.warning(0) box = gui.widgetBox(self.controlArea, orientation="horizontal") gui.rubber(box) self.infoLabel = QLabel() self.infoLabel.setAlignment(Qt.AlignCenter) self.controlArea.layout().addWidget(self.infoLabel) self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.updateItems = [] self.resize(800, 600) self.progress = ProgressState(self, maximum=3) self.progress.valueChanged.connect(self._updateProgress) self.progress.rangeChanged.connect(self._updateProgress) self.executor = ThreadExecutor( threadPool=QThreadPool(maxThreadCount=2) ) task = Task(self, function=self.RetrieveFilesList) task.exceptionReady.connect(self.HandleError) task.start() self._tasks = [] self._haveProgress = False
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) w = self.widget(0) # 'General' tab layout = w.layout() assert isinstance(layout, QFormLayout) cb = QCheckBox(self.tr("Automatically check for updates")) cb.setAttribute(Qt.WA_LayoutUsesWidgetRect) layout.addRow("Updates", cb) self.bind(cb, "checked", "startup/check-updates") # Reporting Tab tab = QWidget() self.addTab(tab, self.tr("Reporting"), toolTip="Settings related to reporting") form = FormLayout() line_edit_mid = QLineEdit() self.bind(line_edit_mid, "text", "reporting/machine-id") form.addRow("Machine ID:", line_edit_mid) box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox( self.tr("Share"), toolTip=self.tr( "Share anonymous usage statistics to improve Orange")) self.bind(cb1, "checked", "reporting/send-statistics") cb1.clicked.connect(UsageStatistics.set_enabled) layout.addWidget(cb1) box.setLayout(layout) form.addRow(self.tr("Anonymous Statistics"), box) label = QLabel( "<a " "href=\"https://orange.biolab.si/statistics-more-info\">" "More info..." "</a>") label.setOpenExternalLinks(True) form.addRow(self.tr(""), label) tab.setLayout(form) # Notifications Tab tab = QWidget() self.addTab(tab, self.tr("Notifications"), toolTip="Settings related to notifications") form = FormLayout() box = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) cb = QCheckBox(self.tr("Enable notifications"), self, toolTip="Pull and display a notification feed.") self.bind(cb, "checked", "notifications/check-notifications") layout.addWidget(cb) box.setLayout(layout) form.addRow(self.tr("On startup"), box) notifs = QWidget(self, objectName="notifications-group") notifs.setLayout(QVBoxLayout()) notifs.layout().setContentsMargins(0, 0, 0, 0) cb1 = QCheckBox( self.tr("Announcements"), self, toolTip="Show notifications about Biolab announcements.\n" "This entails events and courses hosted by the developers of " "Orange.") cb2 = QCheckBox(self.tr("Blog posts"), self, toolTip="Show notifications about blog posts.\n" "We'll only send you the highlights.") cb3 = QCheckBox( self.tr("New features"), self, toolTip="Show notifications about new features in Orange when a new " "version is downloaded and installed,\n" "should the new version entail notable updates.") self.bind(cb1, "checked", "notifications/announcements") self.bind(cb2, "checked", "notifications/blog") self.bind(cb3, "checked", "notifications/new-features") notifs.layout().addWidget(cb1) notifs.layout().addWidget(cb2) notifs.layout().addWidget(cb3) form.addRow(self.tr("Show notifications about"), notifs) tab.setLayout(form)
class FeatureEditor(QFrame): FUNCTIONS = dict( chain( [(key, val) for key, val in math.__dict__.items() if not key.startswith("_")], [(key, val) for key, val in builtins.__dict__.items() if key in {"str", "float", "int", "len", "abs", "max", "min"}])) featureChanged = Signal() featureEdited = Signal() modifiedChanged = Signal(bool) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) layout = QFormLayout(fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow) layout.setContentsMargins(0, 0, 0, 0) self.nameedit = QLineEdit(placeholderText="Name...", sizePolicy=QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed)) self.expressionedit = QLineEdit(placeholderText="Expression...", toolTip=self.ExpressionTooltip) self.attrs_model = itemmodels.VariableListModel(["Select Feature"], parent=self) self.attributescb = ComboBoxSearch( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.attributescb.setModel(self.attrs_model) sorted_funcs = sorted(self.FUNCTIONS) self.funcs_model = itemmodels.PyListModelTooltip() self.funcs_model.setParent(self) self.funcs_model[:] = chain(["Select Function"], sorted_funcs) self.funcs_model.tooltips[:] = chain( [''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs]) self.functionscb = ComboBoxSearch( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.functionscb.setModel(self.funcs_model) hbox = QHBoxLayout() hbox.addWidget(self.attributescb) hbox.addWidget(self.functionscb) layout.addRow(self.nameedit, self.expressionedit) layout.addRow(self.tr(""), hbox) self.setLayout(layout) self.nameedit.editingFinished.connect(self._invalidate) self.expressionedit.textChanged.connect(self._invalidate) self.attributescb.currentIndexChanged.connect(self.on_attrs_changed) self.functionscb.currentIndexChanged.connect(self.on_funcs_changed) self._modified = False def setModified(self, modified): if not isinstance(modified, bool): raise TypeError if self._modified != modified: self._modified = modified self.modifiedChanged.emit(modified) def modified(self): return self._modified modified = Property(bool, modified, setModified, notify=modifiedChanged) def setEditorData(self, data, domain): self.nameedit.setText(data.name) self.expressionedit.setText(data.expression) self.setModified(False) self.featureChanged.emit() self.attrs_model[:] = ["Select Feature"] if domain is not None and not domain.empty(): self.attrs_model[:] += chain(domain.attributes, domain.class_vars, domain.metas) def editorData(self): return FeatureDescriptor(name=self.nameedit.text(), expression=self.nameedit.text()) def _invalidate(self): self.setModified(True) self.featureEdited.emit() self.featureChanged.emit() def on_attrs_changed(self): index = self.attributescb.currentIndex() if index > 0: attr = sanitized_name(self.attrs_model[index].name) self.insert_into_expression(attr) self.attributescb.setCurrentIndex(0) def on_funcs_changed(self): index = self.functionscb.currentIndex() if index > 0: func = self.funcs_model[index] if func in [ "atan2", "fmod", "ldexp", "log", "pow", "copysign", "hypot" ]: self.insert_into_expression(func + "(,)") self.expressionedit.cursorBackward(False, 2) elif func in ["e", "pi"]: self.insert_into_expression(func) else: self.insert_into_expression(func + "()") self.expressionedit.cursorBackward(False) self.functionscb.setCurrentIndex(0) def insert_into_expression(self, what): cp = self.expressionedit.cursorPosition() ct = self.expressionedit.text() text = ct[:cp] + what + ct[cp:] self.expressionedit.setText(text) self.expressionedit.setFocus()
def createEditor(self, parent, _option, _index): return QLineEdit(parent)
def __init__(self): super().__init__() self.data = None self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = False self._init_projector() # Components Selection box = gui.vBox(self.controlArea, "Components Selection") form = QFormLayout() box.layout().addLayout(form) self.components_spin = gui.spin( box, self, "ncomponents", 1, MAX_COMPONENTS, callback=self._update_selection_component_spin, keyboardTracking=False, ) self.components_spin.setSpecialValueText("All") self.variance_spin = gui.spin( box, self, "variance_covered", 1, 100, callback=self._update_selection_variance_spin, keyboardTracking=False, ) self.variance_spin.setSuffix("%") form.addRow("Components:", self.components_spin) form.addRow("Variance covered:", self.variance_spin) # Incremental learning self.sampling_box = gui.vBox(self.controlArea, "Incremental learning") self.addresstext = QLineEdit(box) self.addresstext.setPlaceholderText("Remote server") if self.address: self.addresstext.setText(self.address) self.sampling_box.layout().addWidget(self.addresstext) form = QFormLayout() self.sampling_box.layout().addLayout(form) self.batch_spin = gui.spin( self.sampling_box, self, "batch_size", 50, 100000, step=50, keyboardTracking=False, ) form.addRow("Batch size ~ ", self.batch_spin) self.start_button = gui.button( self.sampling_box, self, "Start remote computation", callback=self.start, autoDefault=False, tooltip="Start/abort computation on the server", ) self.start_button.setEnabled(False) gui.checkBox( self.sampling_box, self, "auto_update", "Periodically fetch model", callback=self.update_model, ) self.__timer = QTimer(self, interval=2000) self.__timer.timeout.connect(self.get_model) self.sampling_box.setVisible(remotely) # Decomposition self.decomposition_box = gui.radioButtons( self.controlArea, self, "decomposition_idx", [d.name for d in DECOMPOSITIONS], box="Decomposition", callback=self._update_decomposition, ) # Options self.options_box = gui.vBox(self.controlArea, "Options") self.normalize_box = gui.checkBox( self.options_box, self, "normalize", "Normalize data", callback=self._update_normalize, ) self.maxp_spin = gui.spin( self.options_box, self, "maxp", 1, MAX_COMPONENTS, label="Show only first", callback=self._setup_plot, keyboardTracking=False, ) self.controlArea.layout().addStretch() gui.auto_commit( self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically", ) self.plot = pg.PlotWidget(background="w") axis = self.plot.getAxis("bottom") axis.setLabel("Principal Components") axis = self.plot.getAxis("left") axis.setLabel("Proportion of variance") self.plot_horlabels = [] self.plot_horlines = [] self.plot.getViewBox().setMenuEnabled(False) self.plot.getViewBox().setMouseEnabled(False, False) self.plot.showGrid(True, True, alpha=0.5) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0)) self.mainArea.layout().addWidget(self.plot) self._update_normalize()
class OWPCA(widget.OWWidget): name = "PCA" description = "Principal component analysis with a scree-diagram." icon = "icons/PCA.svg" priority = 3050 class Inputs: data = Input("Data", Table) class Outputs: transformed_data = Output("Transformed data", Table) components = Output("Components", Table) pca = Output("PCA", PCA, dynamic=False) settingsHandler = settings.DomainContextHandler() ncomponents = settings.Setting(2) variance_covered = settings.Setting(100) batch_size = settings.Setting(100) address = settings.Setting("") auto_update = settings.Setting(True) auto_commit = settings.Setting(True) normalize = settings.ContextSetting(True) decomposition_idx = settings.ContextSetting(0) maxp = settings.Setting(20) axis_labels = settings.Setting(10) graph_name = "plot.plotItem" class Warning(widget.OWWidget.Warning): trivial_components = widget.Msg( "All components of the PCA are trivial (explain 0 variance). " "Input data is constant (or near constant)." ) class Error(widget.OWWidget.Error): no_features = widget.Msg("At least 1 feature is required") no_instances = widget.Msg("At least 1 data instance is required") sparse_data = widget.Msg("Sparse data is not supported") def __init__(self): super().__init__() self.data = None self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = False self._init_projector() # Components Selection box = gui.vBox(self.controlArea, "Components Selection") form = QFormLayout() box.layout().addLayout(form) self.components_spin = gui.spin( box, self, "ncomponents", 1, MAX_COMPONENTS, callback=self._update_selection_component_spin, keyboardTracking=False, ) self.components_spin.setSpecialValueText("All") self.variance_spin = gui.spin( box, self, "variance_covered", 1, 100, callback=self._update_selection_variance_spin, keyboardTracking=False, ) self.variance_spin.setSuffix("%") form.addRow("Components:", self.components_spin) form.addRow("Variance covered:", self.variance_spin) # Incremental learning self.sampling_box = gui.vBox(self.controlArea, "Incremental learning") self.addresstext = QLineEdit(box) self.addresstext.setPlaceholderText("Remote server") if self.address: self.addresstext.setText(self.address) self.sampling_box.layout().addWidget(self.addresstext) form = QFormLayout() self.sampling_box.layout().addLayout(form) self.batch_spin = gui.spin( self.sampling_box, self, "batch_size", 50, 100000, step=50, keyboardTracking=False, ) form.addRow("Batch size ~ ", self.batch_spin) self.start_button = gui.button( self.sampling_box, self, "Start remote computation", callback=self.start, autoDefault=False, tooltip="Start/abort computation on the server", ) self.start_button.setEnabled(False) gui.checkBox( self.sampling_box, self, "auto_update", "Periodically fetch model", callback=self.update_model, ) self.__timer = QTimer(self, interval=2000) self.__timer.timeout.connect(self.get_model) self.sampling_box.setVisible(remotely) # Decomposition self.decomposition_box = gui.radioButtons( self.controlArea, self, "decomposition_idx", [d.name for d in DECOMPOSITIONS], box="Decomposition", callback=self._update_decomposition, ) # Options self.options_box = gui.vBox(self.controlArea, "Options") self.normalize_box = gui.checkBox( self.options_box, self, "normalize", "Normalize data", callback=self._update_normalize, ) self.maxp_spin = gui.spin( self.options_box, self, "maxp", 1, MAX_COMPONENTS, label="Show only first", callback=self._setup_plot, keyboardTracking=False, ) self.controlArea.layout().addStretch() gui.auto_commit( self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically", ) self.plot = pg.PlotWidget(background="w") axis = self.plot.getAxis("bottom") axis.setLabel("Principal Components") axis = self.plot.getAxis("left") axis.setLabel("Proportion of variance") self.plot_horlabels = [] self.plot_horlines = [] self.plot.getViewBox().setMenuEnabled(False) self.plot.getViewBox().setMouseEnabled(False, False) self.plot.showGrid(True, True, alpha=0.5) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0)) self.mainArea.layout().addWidget(self.plot) self._update_normalize() def update_model(self): self.get_model() if self.auto_update and self.rpca and not self.rpca.ready(): self.__timer.start(2000) else: self.__timer.stop() def update_buttons(self, sparse_data=False): if sparse_data: self.normalize = False buttons = self.decomposition_box.buttons for cls, button in zip(DECOMPOSITIONS, buttons): button.setDisabled(sparse_data and not cls.supports_sparse) if not buttons[self.decomposition_idx].isEnabled(): # Set decomposition index to first sparse-enabled decomposition for i, cls in enumerate(DECOMPOSITIONS): if cls.supports_sparse: self.decomposition_idx = i break self._init_projector() def start(self): if "Abort" in self.start_button.text(): self.rpca.abort() self.__timer.stop() self.start_button.setText("Start remote computation") else: self.address = self.addresstext.text() with remote.server(self.address): from Orange.projection.pca import RemotePCA maxiter = (1e5 + self.data.approx_len()) / self.batch_size * 3 self.rpca = RemotePCA(self.data, self.batch_size, int(maxiter)) self.update_model() self.start_button.setText("Abort remote computation") @Inputs.data def set_data(self, data): self.closeContext() self.clear_messages() self.clear() self.start_button.setEnabled(False) self.information() self.data = None if isinstance(data, SqlTable): if data.approx_len() < AUTO_DL_LIMIT: data = Table(data) elif not remotely: self.information("Data has been sampled") data_sample = data.sample_time(1, no_cache=True) data_sample.download_data(2000, partial=True) data = Table(data_sample) else: # data was big and remote available self.sampling_box.setVisible(True) self.start_button.setText("Start remote computation") self.start_button.setEnabled(True) if not isinstance(data, SqlTable): self.sampling_box.setVisible(False) if isinstance(data, Table): if len(data.domain.attributes) == 0: self.Error.no_features() self.clear_outputs() return if len(data) == 0: self.Error.no_instances() self.clear_outputs() return self.openContext(data) sparse_data = data is not None and data.is_sparse() self.normalize_box.setDisabled(sparse_data) self.update_buttons(sparse_data=sparse_data) self.data = data self.fit() def fit(self): self.clear() self.Warning.trivial_components.clear() if self.data is None: return data = self.data self._pca_projector.preprocessors = self._pca_preprocessors + ( [Normalize()] if self.normalize else [] ) if not isinstance(data, SqlTable): pca = self._pca_projector(data) variance_ratio = pca.explained_variance_ratio_ cumulative = numpy.cumsum(variance_ratio) if numpy.isfinite(cumulative[-1]): self.components_spin.setRange(0, len(cumulative)) self._pca = pca self._variance_ratio = variance_ratio self._cumulative = cumulative self._setup_plot() else: self.Warning.trivial_components() self.unconditional_commit() def clear(self): self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = None self.plot_horlabels = [] self.plot_horlines = [] self.plot.clear() def clear_outputs(self): self.Outputs.transformed_data.send(None) self.Outputs.components.send(None) self.Outputs.pca.send(self._pca_projector) def get_model(self): if self.rpca is None: return if self.rpca.ready(): self.__timer.stop() self.start_button.setText("Restart (finished)") self._pca = self.rpca.get_state() if self._pca is None: return self._variance_ratio = self._pca.explained_variance_ratio_ self._cumulative = numpy.cumsum(self._variance_ratio) self._setup_plot() self._transformed = None self.commit() def _setup_plot(self): self.plot.clear() if self._pca is None: return explained_ratio = self._variance_ratio explained = self._cumulative p = min(len(self._variance_ratio), self.maxp) self.plot.plot( numpy.arange(p), explained_ratio[:p], pen=pg.mkPen(QColor(Qt.red), width=2), antialias=True, name="Variance", ) self.plot.plot( numpy.arange(p), explained[:p], pen=pg.mkPen(QColor(Qt.darkYellow), width=2), antialias=True, name="Cumulative Variance", ) cutpos = self._nselected_components() - 1 self._line = pg.InfiniteLine( angle=90, pos=cutpos, movable=True, bounds=(0, p - 1) ) self._line.setCursor(Qt.SizeHorCursor) self._line.setPen(pg.mkPen(QColor(Qt.black), width=2)) self._line.sigPositionChanged.connect(self._on_cut_changed) self.plot.addItem(self._line) self.plot_horlines = ( pg.PlotCurveItem(pen=pg.mkPen(QColor(Qt.blue), style=Qt.DashLine)), pg.PlotCurveItem(pen=pg.mkPen(QColor(Qt.blue), style=Qt.DashLine)), ) self.plot_horlabels = ( pg.TextItem(color=QColor(Qt.black), anchor=(1, 0)), pg.TextItem(color=QColor(Qt.black), anchor=(1, 1)), ) for item in self.plot_horlabels + self.plot_horlines: self.plot.addItem(item) self._set_horline_pos() self.plot.setRange(xRange=(0.0, p - 1), yRange=(0.0, 1.0)) self._update_axis() def _set_horline_pos(self): cutidx = self.ncomponents - 1 for line, label, curve in zip( self.plot_horlines, self.plot_horlabels, (self._variance_ratio, self._cumulative), ): y = curve[cutidx] line.setData([-1, cutidx], 2 * [y]) label.setPos(cutidx, y) label.setPlainText("{:.3f}".format(y)) def _on_cut_changed(self, line): # cut changed by means of a cut line over the scree plot. value = int(round(line.value())) self._line.setValue(value) current = self._nselected_components() components = value + 1 if not (self.ncomponents == 0 and components == len(self._variance_ratio)): self.ncomponents = components self._set_horline_pos() if self._pca is not None: var = self._cumulative[components - 1] if numpy.isfinite(var): self.variance_covered = int(var * 100) if current != self._nselected_components(): self._invalidate_selection() def _update_selection_component_spin(self): # cut changed by "ncomponents" spin. if self._pca is None: self._invalidate_selection() return if self.ncomponents == 0: # Special "All" value cut = len(self._variance_ratio) else: cut = self.ncomponents var = self._cumulative[cut - 1] if numpy.isfinite(var): self.variance_covered = int(var * 100) if numpy.floor(self._line.value()) + 1 != cut: self._line.setValue(cut - 1) self._invalidate_selection() def _update_selection_variance_spin(self): # cut changed by "max variance" spin. if self._pca is None: return cut = numpy.searchsorted(self._cumulative, self.variance_covered / 100.0) + 1 cut = min(cut, len(self._cumulative)) self.ncomponents = cut if numpy.floor(self._line.value()) + 1 != cut: self._line.setValue(cut - 1) self._invalidate_selection() def _update_normalize(self): self.fit() if self.data is None: self._invalidate_selection() def _init_projector(self): cls = DECOMPOSITIONS[self.decomposition_idx] self._pca_projector = cls(n_components=MAX_COMPONENTS) self._pca_projector.component = self.ncomponents self._pca_preprocessors = cls.preprocessors def _update_decomposition(self): self._init_projector() self._update_normalize() def _nselected_components(self): """Return the number of selected components.""" if self._pca is None: return 0 if self.ncomponents == 0: # Special "All" value max_comp = len(self._variance_ratio) else: max_comp = self.ncomponents var_max = self._cumulative[max_comp - 1] if var_max != numpy.floor(self.variance_covered / 100.0): cut = max_comp assert numpy.isfinite(var_max) self.variance_covered = int(var_max * 100) else: self.ncomponents = cut = ( numpy.searchsorted(self._cumulative, self.variance_covered / 100.0) + 1 ) return cut def _invalidate_selection(self): self.commit() def _update_axis(self): p = min(len(self._variance_ratio), self.maxp) axis = self.plot.getAxis("bottom") d = max((p - 1) // (self.axis_labels - 1), 1) axis.setTicks([[(i, str(i + 1)) for i in range(0, p, d)]]) def commit(self): transformed = components = None if self._pca is not None: if self._transformed is None: # Compute the full transform (MAX_COMPONENTS components) only once. self._transformed = self._pca(self.data) transformed = self._transformed domain = Domain( transformed.domain.attributes[: self.ncomponents], self.data.domain.class_vars, self.data.domain.metas, ) transformed = transformed.from_table(domain, transformed) # prevent caching new features by defining compute_value dom = Domain( [ ContinuousVariable(a.name, compute_value=lambda _: None) for a in self._pca.orig_domain.attributes ], metas=[StringVariable(name="component")], ) metas = numpy.array( [["PC{}".format(i + 1) for i in range(self.ncomponents)]], dtype=object ).T components = Table( dom, self._pca.components_[: self.ncomponents], metas=metas ) components.name = "components" self._pca_projector.component = self.ncomponents self.Outputs.transformed_data.send(transformed) self.Outputs.components.send(components) self.Outputs.pca.send(self._pca_projector) def send_report(self): if self.data is None: return self.report_items( ( ("Decomposition", DECOMPOSITIONS[self.decomposition_idx].name), ("Normalize data", str(self.normalize)), ("Selected components", self.ncomponents), ("Explained variance", "{:.3f} %".format(self.variance_covered)), ) ) self.report_plot() @classmethod def migrate_settings(cls, settings, version): if "variance_covered" in settings: # Due to the error in gh-1896 the variance_covered was persisted # as a NaN value, causing a TypeError in the widgets `__init__`. vc = settings["variance_covered"] if isinstance(vc, numbers.Real): if numpy.isfinite(vc): vc = int(vc) else: vc = 100 settings["variance_covered"] = vc if settings.get("ncomponents", 0) > MAX_COMPONENTS: settings["ncomponents"] = MAX_COMPONENTS
class FeatureEditor(QFrame): FUNCTIONS = dict(chain([(key, val) for key, val in math.__dict__.items() if not key.startswith("_")], [(key, val) for key, val in builtins.__dict__.items() if key in {"str", "float", "int", "len", "abs", "max", "min"}])) featureChanged = Signal() featureEdited = Signal() modifiedChanged = Signal(bool) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) layout = QFormLayout( fieldGrowthPolicy=QFormLayout.ExpandingFieldsGrow ) layout.setContentsMargins(0, 0, 0, 0) self.nameedit = QLineEdit( placeholderText="Name...", sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) ) self.expressionedit = QLineEdit( placeholderText="Expression..." ) self.attrs_model = itemmodels.VariableListModel( ["Select Feature"], parent=self) self.attributescb = QComboBox( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) ) self.attributescb.setModel(self.attrs_model) sorted_funcs = sorted(self.FUNCTIONS) self.funcs_model = itemmodels.PyListModelTooltip() self.funcs_model.setParent(self) self.funcs_model[:] = chain(["Select Function"], sorted_funcs) self.funcs_model.tooltips[:] = chain( [''], [self.FUNCTIONS[func].__doc__ for func in sorted_funcs]) self.functionscb = QComboBox( minimumContentsLength=16, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)) self.functionscb.setModel(self.funcs_model) hbox = QHBoxLayout() hbox.addWidget(self.attributescb) hbox.addWidget(self.functionscb) layout.addRow(self.nameedit, self.expressionedit) layout.addRow(self.tr(""), hbox) self.setLayout(layout) self.nameedit.editingFinished.connect(self._invalidate) self.expressionedit.textChanged.connect(self._invalidate) self.attributescb.currentIndexChanged.connect(self.on_attrs_changed) self.functionscb.currentIndexChanged.connect(self.on_funcs_changed) self._modified = False def setModified(self, modified): if not type(modified) is bool: raise TypeError if self._modified != modified: self._modified = modified self.modifiedChanged.emit(modified) def modified(self): return self._modified modified = Property(bool, modified, setModified, notify=modifiedChanged) def setEditorData(self, data, domain): self.nameedit.setText(data.name) self.expressionedit.setText(data.expression) self.setModified(False) self.featureChanged.emit() self.attrs_model[:] = ["Select Feature"] if domain is not None and (domain or domain.metas): self.attrs_model[:] += chain(domain.attributes, domain.class_vars, domain.metas) def editorData(self): return FeatureDescriptor(name=self.nameedit.text(), expression=self.nameedit.text()) def _invalidate(self): self.setModified(True) self.featureEdited.emit() self.featureChanged.emit() def on_attrs_changed(self): index = self.attributescb.currentIndex() if index > 0: attr = sanitized_name(self.attrs_model[index].name) self.insert_into_expression(attr) self.attributescb.setCurrentIndex(0) def on_funcs_changed(self): index = self.functionscb.currentIndex() if index > 0: func = self.funcs_model[index] if func in ["atan2", "fmod", "ldexp", "log", "pow", "copysign", "hypot"]: self.insert_into_expression(func + "(,)") self.expressionedit.cursorBackward(False, 2) elif func in ["e", "pi"]: self.insert_into_expression(func) else: self.insert_into_expression(func + "()") self.expressionedit.cursorBackward(False) self.functionscb.setCurrentIndex(0) def insert_into_expression(self, what): cp = self.expressionedit.cursorPosition() ct = self.expressionedit.text() text = ct[:cp] + what + ct[cp:] self.expressionedit.setText(text) self.expressionedit.setFocus()
class OWSql(OWWidget): name = "SQL Table" id = "orange.widgets.data.sql" description = "Load data set from SQL." icon = "icons/SQLTable.svg" priority = 30 category = "Data" keywords = ["data", "file", "load", "read"] class Outputs: data = Output( "Data", Table, doc="Attribute-valued data set read from the input file.") settings_version = 2 want_main_area = False resizing_enabled = False host = Setting(None) port = Setting(None) database = Setting(None) schema = Setting(None) username = "" password = "" table = Setting(None) sql = Setting("") guess_values = Setting(True) download = Setting(False) materialize = Setting(False) materialize_table_name = Setting("") class Information(OWWidget.Information): data_sampled = Msg("Data description was generated from a sample.") class Error(OWWidget.Error): connection = Msg("{}") no_backends = Msg("Please install a backend to use this widget") missing_extension = Msg("Database is missing extension{}: {}") def __init__(self): super().__init__() self.backend = None self.data_desc_table = None self.database_desc = None vbox = gui.vBox(self.controlArea, "Server", addSpace=True) box = gui.vBox(vbox) self.backendmodel = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) if len(self.backendmodel): self.backendcombo.setModel(self.backendmodel) else: self.Error.no_backends() box.setEnabled(False) box.layout().addWidget(self.backendcombo) self.servertext = QLineEdit(box) self.servertext.setPlaceholderText('Server') self.servertext.setToolTip('Server') self.servertext.editingFinished.connect(self._load_credentials) if self.host: self.servertext.setText(self.host if not self.port else '{}:{}'. format(self.host, self.port)) box.layout().addWidget(self.servertext) self.databasetext = QLineEdit(box) self.databasetext.setPlaceholderText('Database[/Schema]') self.databasetext.setToolTip('Database or optionally Database/Schema') if self.database: self.databasetext.setText( self.database if not self.schema else '{}/{}'. format(self.database, self.schema)) box.layout().addWidget(self.databasetext) self.usernametext = QLineEdit(box) self.usernametext.setPlaceholderText('Username') self.usernametext.setToolTip('Username') box.layout().addWidget(self.usernametext) self.passwordtext = QLineEdit(box) self.passwordtext.setPlaceholderText('Password') self.passwordtext.setToolTip('Password') self.passwordtext.setEchoMode(QLineEdit.Password) box.layout().addWidget(self.passwordtext) self._load_credentials() tables = gui.hBox(box) self.tablemodel = TableModel() self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength) self.tablecombo.setModel(self.tablemodel) self.tablecombo.setToolTip('table') tables.layout().addWidget(self.tablecombo) self.tablecombo.activated[int].connect(self.select_table) self.connectbutton = gui.button(tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) tables.layout().addWidget(self.connectbutton) self.custom_sql = gui.vBox(box) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) self.custom_sql.layout().addWidget(self.sqltext) mt = gui.hBox(self.custom_sql) cb = gui.checkBox(mt, self, 'materialize', 'Materialize to table ') cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') self.executebtn = gui.button(self.custom_sql, self, 'Execute', callback=self.open_table) box.layout().addWidget(self.custom_sql) gui.checkBox(box, self, "guess_values", "Auto-discover categorical variables", callback=self.open_table) gui.checkBox(box, self, "download", "Download data to local memory", callback=self.open_table) gui.rubber(self.buttonsArea) QTimer.singleShot(0, self.connect) def _load_credentials(self): self._parse_host_port() cm = self._credential_manager(self.host, self.port) self.username = cm.username self.password = cm.password if self.username: self.usernametext.setText(self.username) if self.password: self.passwordtext.setText(self.password) def _save_credentials(self): cm = self._credential_manager(self.host, self.port) cm.username = self.username cm.password = self.password def _credential_manager(self, host, port): return CredentialManager("SQL Table: {}:{}".format(host, port)) def error(self, id=0, text=""): super().error(id, text) err_style = 'QLineEdit {border: 2px solid red;}' if 'server' in text or 'host' in text: self.servertext.setStyleSheet(err_style) else: self.servertext.setStyleSheet('') if 'role' in text: self.usernametext.setStyleSheet(err_style) else: self.usernametext.setStyleSheet('') if 'database' in text: self.databasetext.setStyleSheet(err_style) else: self.databasetext.setStyleSheet('') def _parse_host_port(self): hostport = self.servertext.text().split(':') self.host = hostport[0] self.port = hostport[1] if len(hostport) == 2 else None def connect(self): self._parse_host_port() self.database, _, self.schema = self.databasetext.text().partition('/') self.username = self.usernametext.text() or None self.password = self.passwordtext.text() or None try: if self.backendcombo.currentIndex() < 0: return backend = self.backendmodel[self.backendcombo.currentIndex()] self.backend = backend( dict(host=self.host, port=self.port, database=self.database, user=self.username, password=self.password)) self.Error.connection.clear() self._save_credentials() self.database_desc = OrderedDict( (("Host", self.host), ("Port", self.port), ("Database", self.database), ("User name", self.username))) self.refresh_tables() self.select_table() except BackendError as err: error = str(err).split('\n')[0] self.Error.connection(error) self.database_desc = self.data_desc_table = None self.tablecombo.clear() def refresh_tables(self): self.tablemodel.clear() self.Error.missing_extension.clear() if self.backend is None: self.data_desc_table = None return self.tablemodel.append("Select a table") self.tablemodel.extend(self.backend.list_tables(self.schema)) self.tablemodel.append("Custom SQL") def select_table(self): curIdx = self.tablecombo.currentIndex() if self.tablecombo.itemText(curIdx) != "Custom SQL": self.custom_sql.setVisible(False) return self.open_table() else: self.custom_sql.setVisible(True) self.data_desc_table = None self.database_desc["Table"] = "(None)" self.table = None #self.Error.missing_extension( # 's' if len(missing) > 1 else '', # ', '.join(missing), # shown=missing) def open_table(self): table = self.get_table() self.data_desc_table = table self.Outputs.data.send(table) def get_table(self): if self.tablecombo.currentIndex() <= 0: if self.database_desc: self.database_desc["Table"] = "(None)" self.data_desc_table = None return if self.tablecombo.currentIndex() < self.tablecombo.count() - 1: self.table = self.tablemodel[self.tablecombo.currentIndex()] self.database_desc["Table"] = self.table if "Query" in self.database_desc: del self.database_desc["Query"] else: self.sql = self.table = self.sqltext.toPlainText() if self.materialize: import psycopg2 if not self.materialize_table_name: self.Error.connection( "Specify a table name to materialize the query") return try: with self.backend.execute_sql_query( "DROP TABLE IF EXISTS " + self.materialize_table_name): pass with self.backend.execute_sql_query( "CREATE TABLE " + self.materialize_table_name + " AS " + self.table): pass with self.backend.execute_sql_query( "ANALYZE " + self.materialize_table_name): pass self.table = self.materialize_table_name except (psycopg2.ProgrammingError, BackendError) as ex: self.Error.connection(str(ex)) return try: table = SqlTable(dict(host=self.host, port=self.port, database=self.database, user=self.username, password=self.password), self.table, backend=type(self.backend), inspect_values=False) except BackendError as ex: self.Error.connection(str(ex)) return self.Error.connection.clear() sample = False if table.approx_len() > LARGE_TABLE and self.guess_values: confirm = QMessageBox(self) confirm.setIcon(QMessageBox.Warning) confirm.setText("Attribute discovery might take " "a long time on large tables.\n" "Do you want to auto discover attributes?") confirm.addButton("Yes", QMessageBox.YesRole) no_button = confirm.addButton("No", QMessageBox.NoRole) sample_button = confirm.addButton("Yes, on a sample", QMessageBox.YesRole) confirm.exec() if confirm.clickedButton() == no_button: self.guess_values = False elif confirm.clickedButton() == sample_button: sample = True self.Information.clear() if self.guess_values: QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) if sample: s = table.sample_time(1) domain = s.get_domain(inspect_values=True) self.Information.data_sampled() else: domain = table.get_domain(inspect_values=True) QApplication.restoreOverrideCursor() table.domain = domain if self.download: if table.approx_len() > MAX_DL_LIMIT: QMessageBox.warning( self, 'Warning', "Data is too big to download.\n" "Consider using the Data Sampler widget to download " "a sample instead.") self.download = False elif table.approx_len() > AUTO_DL_LIMIT: confirm = QMessageBox.question( self, 'Question', "Data appears to be big. Do you really " "want to download it to local memory?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if confirm == QMessageBox.No: self.download = False if self.download: table.download_data(MAX_DL_LIMIT) table = Table(table) return table def send_report(self): if not self.database_desc: self.report_paragraph("No database connection.") return self.report_items("Database", self.database_desc) if self.data_desc_table: self.report_items("Data", report.describe_data(self.data_desc_table)) @classmethod def migrate_settings(cls, settings, version): if version < 2: # Until Orange version 3.4.4 username and password had been stored # in Settings. cm = cls._credential_manager(settings["host"], settings["port"]) cm.username = settings["username"] cm.password = settings["password"]
def __init__(self): super().__init__() self.data = None self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = False self._init_projector() # Components Selection box = gui.vBox(self.controlArea, "Components Selection") form = QFormLayout() box.layout().addLayout(form) self.components_spin = gui.spin( box, self, "ncomponents", 1, MAX_COMPONENTS, callback=self._update_selection_component_spin, keyboardTracking=False ) self.components_spin.setSpecialValueText("All") self.variance_spin = gui.spin( box, self, "variance_covered", 1, 100, callback=self._update_selection_variance_spin, keyboardTracking=False ) self.variance_spin.setSuffix("%") form.addRow("Components:", self.components_spin) form.addRow("Variance covered:", self.variance_spin) # Incremental learning self.sampling_box = gui.vBox(self.controlArea, "Incremental learning") self.addresstext = QLineEdit(box) self.addresstext.setPlaceholderText('Remote server') if self.address: self.addresstext.setText(self.address) self.sampling_box.layout().addWidget(self.addresstext) form = QFormLayout() self.sampling_box.layout().addLayout(form) self.batch_spin = gui.spin( self.sampling_box, self, "batch_size", 50, 100000, step=50, keyboardTracking=False) form.addRow("Batch size ~ ", self.batch_spin) self.start_button = gui.button( self.sampling_box, self, "Start remote computation", callback=self.start, autoDefault=False, tooltip="Start/abort computation on the server") self.start_button.setEnabled(False) gui.checkBox(self.sampling_box, self, "auto_update", "Periodically fetch model", callback=self.update_model) self.__timer = QTimer(self, interval=2000) self.__timer.timeout.connect(self.get_model) self.sampling_box.setVisible(remotely) # Decomposition self.decomposition_box = gui.radioButtons( self.controlArea, self, "decomposition_idx", [d.name for d in DECOMPOSITIONS], box="Decomposition", callback=self._update_decomposition ) # Options self.options_box = gui.vBox(self.controlArea, "Options") self.normalize_box = gui.checkBox( self.options_box, self, "normalize", "Normalize data", callback=self._update_normalize ) self.maxp_spin = gui.spin( self.options_box, self, "maxp", 1, MAX_COMPONENTS, label="Show only first", callback=self._setup_plot, keyboardTracking=False ) self.controlArea.layout().addStretch() gui.auto_commit(self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.plot = pg.PlotWidget(background="w") axis = self.plot.getAxis("bottom") axis.setLabel("Principal Components") axis = self.plot.getAxis("left") axis.setLabel("Proportion of variance") self.plot_horlabels = [] self.plot_horlines = [] self.plot.getViewBox().setMenuEnabled(False) self.plot.getViewBox().setMouseEnabled(False, False) self.plot.showGrid(True, True, alpha=0.5) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0)) self.mainArea.layout().addWidget(self.plot) self._update_normalize()
def __init__(self): super().__init__() self.backend = None self.data_desc_table = None self.database_desc = None vbox = gui.vBox(self.controlArea, "Server", addSpace=True) box = gui.vBox(vbox) self.backendmodel = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) if len(self.backendmodel): self.backendcombo.setModel(self.backendmodel) else: self.Error.no_backends() box.setEnabled(False) box.layout().addWidget(self.backendcombo) self.servertext = QLineEdit(box) self.servertext.setPlaceholderText('Server') self.servertext.setToolTip('Server') self.servertext.editingFinished.connect(self._load_credentials) if self.host: self.servertext.setText(self.host if not self.port else '{}:{}'. format(self.host, self.port)) box.layout().addWidget(self.servertext) self.databasetext = QLineEdit(box) self.databasetext.setPlaceholderText('Database[/Schema]') self.databasetext.setToolTip('Database or optionally Database/Schema') if self.database: self.databasetext.setText( self.database if not self.schema else '{}/{}'. format(self.database, self.schema)) box.layout().addWidget(self.databasetext) self.usernametext = QLineEdit(box) self.usernametext.setPlaceholderText('Username') self.usernametext.setToolTip('Username') box.layout().addWidget(self.usernametext) self.passwordtext = QLineEdit(box) self.passwordtext.setPlaceholderText('Password') self.passwordtext.setToolTip('Password') self.passwordtext.setEchoMode(QLineEdit.Password) box.layout().addWidget(self.passwordtext) self._load_credentials() tables = gui.hBox(box) self.tablemodel = TableModel() self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength) self.tablecombo.setModel(self.tablemodel) self.tablecombo.setToolTip('table') tables.layout().addWidget(self.tablecombo) self.tablecombo.activated[int].connect(self.select_table) self.connectbutton = gui.button(tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) tables.layout().addWidget(self.connectbutton) self.custom_sql = gui.vBox(box) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) self.custom_sql.layout().addWidget(self.sqltext) mt = gui.hBox(self.custom_sql) cb = gui.checkBox(mt, self, 'materialize', 'Materialize to table ') cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') self.executebtn = gui.button(self.custom_sql, self, 'Execute', callback=self.open_table) box.layout().addWidget(self.custom_sql) gui.checkBox(box, self, "guess_values", "Auto-discover categorical variables", callback=self.open_table) gui.checkBox(box, self, "download", "Download data to local memory", callback=self.open_table) gui.rubber(self.buttonsArea) QTimer.singleShot(0, self.connect)
class OWDataSets(widget.OWWidget): name = "Data Sets" description = "Load a data set from an online repository" icon = "icons/DataSets.svg" priority = 20 replaces = ["orangecontrib.prototypes.widgets.owdatasets.OWDataSets"] # The following constants can be overridden in a subclass # to reuse this widget for a different repository # Take care when refactoring! (used in e.g. single-cell) INDEX_URL = "http://datasets.orange.biolab.si/" DATASET_DIR = "datasets" class Error(widget.OWWidget.Error): no_remote_datasets = Msg("Could not fetch data set list") class Warning(widget.OWWidget.Warning): only_local_datasets = Msg("Could not fetch data sets list, only local " "cached data sets are shown") class Outputs: data = Output("Data", Orange.data.Table) #: Selected data set id selected_id = settings.Setting(None) # type: Optional[str] auto_commit = settings.Setting(False) # type: bool #: main area splitter state splitter_state = settings.Setting(b'') # type: bytes header_state = settings.Setting(b'') # type: bytes def __init__(self): super().__init__() self.local_cache_path = os.path.join(data_dir(), self.DATASET_DIR) self.__awaiting_state = None # type: Optional[_FetchState] box = gui.widgetBox(self.controlArea, "Info") self.infolabel = QLabel(text="Initializing...\n\n") box.layout().addWidget(self.infolabel) gui.widgetLabel(self.mainArea, "Filter") self.filterLineEdit = QLineEdit( textChanged=self.filter ) self.mainArea.layout().addWidget(self.filterLineEdit) self.splitter = QSplitter(orientation=Qt.Vertical) self.view = QTreeView( sortingEnabled=True, selectionMode=QTreeView.SingleSelection, alternatingRowColors=True, rootIsDecorated=False, editTriggers=QTreeView.NoEditTriggers, ) box = gui.widgetBox(self.splitter, "Description", addToLayout=False) self.descriptionlabel = QLabel( wordWrap=True, textFormat=Qt.RichText, ) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) ) self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) self.splitter.addWidget(self.view) self.splitter.addWidget(box) self.splitter.setSizes([300, 200]) self.splitter.splitterMoved.connect( lambda: setattr(self, "splitter_state", bytes(self.splitter.saveState())) ) self.mainArea.layout().addWidget(self.splitter) self.controlArea.layout().addStretch(10) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Data") model = QStandardItemModel(self) model.setHorizontalHeaderLabels(HEADER) proxy = QSortFilterProxyModel() proxy.setSourceModel(model) proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) if self.splitter_state: self.splitter.restoreState(self.splitter_state) self.view.setItemDelegateForColumn( Header.Size, SizeDelegate(self)) self.view.setItemDelegateForColumn( Header.Local, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.view.setItemDelegateForColumn( Header.Instances, NumericalDelegate(self)) self.view.setItemDelegateForColumn( Header.Variables, NumericalDelegate(self)) self.view.resizeColumnToContents(Header.Local) if self.header_state: self.view.header().restoreState(self.header_state) self.setBlocking(True) self.setStatusMessage("Initializing") self._executor = ThreadPoolExecutor(max_workers=1) f = self._executor.submit(self.list_remote) w = FutureWatcher(f, parent=self) w.done.connect(self.__set_index) @Slot(object) def __set_index(self, f): # type: (Future) -> None # set results from `list_remote` query. assert QThread.currentThread() is self.thread() assert f.done() self.setBlocking(False) self.setStatusMessage("") allinfolocal = self.list_local() try: res = f.result() except Exception: log.exception("Error while fetching updated index") if not allinfolocal: self.Error.no_remote_datasets() else: self.Warning.only_local_datasets() res = {} allinforemote = res # type: Dict[Tuple[str, str], dict] allkeys = set(allinfolocal) if allinforemote is not None: allkeys = allkeys | set(allinforemote) allkeys = sorted(allkeys) def info(file_path): if file_path in allinforemote: info = allinforemote[file_path] else: info = allinfolocal[file_path] islocal = file_path in allinfolocal isremote = file_path in allinforemote outdated = islocal and isremote and ( allinforemote[file_path].get('version', '') != allinfolocal[file_path].get('version', '')) islocal &= not outdated prefix = os.path.join('', *file_path[:-1]) filename = file_path[-1] return namespace( prefix=prefix, filename=filename, title=info.get("title", filename), datetime=info.get("datetime", None), description=info.get("description", None), references=info.get("references", []), seealso=info.get("seealso", []), source=info.get("source", None), year=info.get("year", None), instances=info.get("instances", None), variables=info.get("variables", None), target=info.get("target", None), missing=info.get("missing", None), tags=info.get("tags", []), size=info.get("size", None), islocal=islocal, outdated=outdated ) model = QStandardItemModel(self) model.setHorizontalHeaderLabels(HEADER) current_index = -1 for i, file_path in enumerate(allkeys): datainfo = info(file_path) item1 = QStandardItem() item1.setData(" " if datainfo.islocal else "", Qt.DisplayRole) item1.setData(datainfo, Qt.UserRole) item2 = QStandardItem(datainfo.title) item3 = QStandardItem() item3.setData(datainfo.size, Qt.DisplayRole) item4 = QStandardItem() item4.setData(datainfo.instances, Qt.DisplayRole) item5 = QStandardItem() item5.setData(datainfo.variables, Qt.DisplayRole) item6 = QStandardItem() item6.setData(datainfo.target, Qt.DisplayRole) if datainfo.target: item6.setIcon(variable_icon(datainfo.target)) item7 = QStandardItem() item7.setData(", ".join(datainfo.tags) if datainfo.tags else "", Qt.DisplayRole) row = [item1, item2, item3, item4, item5, item6, item7] model.appendRow(row) if os.path.join(*file_path) == self.selected_id: current_index = i hs = self.view.header().saveState() model_ = self.view.model().sourceModel() self.view.model().setSourceModel(model) self.view.header().restoreState(hs) model_.deleteLater() model_.setParent(None) self.view.selectionModel().selectionChanged.connect( self.__on_selection ) # Update the info text self.infolabel.setText(format_info(model.rowCount(), len(allinfolocal))) if current_index != -1: selmodel = self.view.selectionModel() selmodel.select( self.view.model().mapFromSource(model.index(current_index, 0)), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) def __update_cached_state(self): model = self.view.model().sourceModel() localinfo = self.list_local() assert isinstance(model, QStandardItemModel) allinfo = [] for i in range(model.rowCount()): item = model.item(i, 0) info = item.data(Qt.UserRole) info.islocal = (info.prefix, info.filename) in localinfo item.setData(" " if info.islocal else "", Qt.DisplayRole) allinfo.append(info) self.infolabel.setText(format_info( model.rowCount(), sum(info.islocal for info in allinfo))) def selected_dataset(self): """ Return the current selected data set info or None if not selected Returns ------- info : Optional[namespace] """ rows = self.view.selectionModel().selectedRows(0) assert 0 <= len(rows) <= 1 current = rows[0] if rows else None # type: Optional[QModelIndex] if current is not None: info = current.data(Qt.UserRole) assert isinstance(info, namespace) else: info = None return info def filter(self): filter_string = self.filterLineEdit.text().strip() proxyModel = self.view.model() if proxyModel: proxyModel.setFilterFixedString(filter_string) def __on_selection(self): # Main data sets view selection has changed rows = self.view.selectionModel().selectedRows(0) assert 0 <= len(rows) <= 1 current = rows[0] if rows else None # type: Optional[QModelIndex] if current is not None: current = self.view.model().mapToSource(current) di = current.data(Qt.UserRole) text = description_html(di) self.descriptionlabel.setText(text) self.selected_id = os.path.join(di.prefix, di.filename) else: self.descriptionlabel.setText("") self.selected_id = None self.commit() def commit(self): """ Commit a dataset to the output immediately (if available locally) or schedule download background and an eventual send. During the download the widget is in blocking state (OWWidget.isBlocking) """ di = self.selected_dataset() if di is not None: self.Error.clear() if self.__awaiting_state is not None: # disconnect from the __commit_complete self.__awaiting_state.watcher.done.disconnect( self.__commit_complete) # .. and connect to update_cached_state # self.__awaiting_state.watcher.done.connect( # self.__update_cached_state) # TODO: There are possible pending __progress_advance queued self.__awaiting_state.pb.advance.disconnect( self.__progress_advance) self.progressBarFinished(processEvents=None) self.__awaiting_state = None if not di.islocal: pr = progress() callback = lambda pr=pr: pr.advance.emit() pr.advance.connect(self.__progress_advance, Qt.QueuedConnection) self.progressBarInit(processEvents=None) self.setStatusMessage("Fetching...") self.setBlocking(True) f = self._executor.submit( ensure_local, self.INDEX_URL, di.prefix, di.filename, self.local_cache_path, force=di.outdated, progress_advance=callback) w = FutureWatcher(f, parent=self) w.done.connect(self.__commit_complete) self.__awaiting_state = _FetchState(f, w, pr) else: self.setStatusMessage("") self.setBlocking(False) self.commit_cached(di.prefix, di.filename) else: self.Outputs.data.send(None) @Slot(object) def __commit_complete(self, f): # complete the commit operation after the required file has been # downloaded assert QThread.currentThread() is self.thread() assert self.__awaiting_state is not None assert self.__awaiting_state.future is f if self.isBlocking(): self.progressBarFinished(processEvents=None) self.setBlocking(False) self.setStatusMessage("") self.__awaiting_state = None try: path = f.result() except Exception as ex: log.exception("Error:") self.error(format_exception(ex)) path = None self.__update_cached_state() if path is not None: data = Orange.data.Table(path) else: data = None self.Outputs.data.send(data) def commit_cached(self, prefix, filename): path = LocalFiles(self.local_cache_path).localpath(prefix, filename) self.Outputs.data.send(Orange.data.Table(path)) @Slot() def __progress_advance(self): assert QThread.currentThread() is self.thread() self.progressBarAdvance(1, processEvents=None) def onDeleteWidget(self): super().onDeleteWidget() if self.__awaiting_state is not None: self.__awaiting_state.watcher.done.disconnect(self.__commit_complete) self.__awaiting_state.pb.advance.disconnect(self.__progress_advance) self.__awaiting_state = None def sizeHint(self): return QSize(900, 600) def closeEvent(self, event): self.splitter_state = bytes(self.splitter.saveState()) self.header_state = bytes(self.view.header().saveState()) super().closeEvent(event) def list_remote(self): # type: () -> Dict[Tuple[str, str], dict] client = ServerFiles(server=self.INDEX_URL) return client.allinfo() def list_local(self): # type: () -> Dict[Tuple[str, str], dict] return LocalFiles(self.local_cache_path).allinfo()
def __run_add_package_dialog(self): dlg = QDialog(self, windowTitle="按名称添加附加组件") dlg.setAttribute(Qt.WA_DeleteOnClose) vlayout = QVBoxLayout() form = QFormLayout() form.setContentsMargins(0, 0, 0, 0) nameentry = QLineEdit( placeholderText="包名", toolTip="输入PyPI上显示的包名称(忽略大写)") nameentry.setMinimumWidth(250) form.addRow("名称:", nameentry) vlayout.addLayout(form) buttons = QDialogButtonBox( standardButtons=QDialogButtonBox.Ok | QDialogButtonBox.Cancel ) okb = buttons.button(QDialogButtonBox.Ok) okb.setEnabled(False) okb.setText("添加") buttons.button(QDialogButtonBox.Cancel).setText("取消") def changed(name): okb.setEnabled(bool(name)) nameentry.textChanged.connect(changed) vlayout.addWidget(buttons) vlayout.setSizeConstraint(QVBoxLayout.SetFixedSize) dlg.setLayout(vlayout) f = None def query(): nonlocal f name = nameentry.text() f = self._executor.submit(pypi_json_query_project_meta, [name]) okb.setDisabled(True) def ondone(f): error_text = "" error_details = "" try: pkgs = f.result() except Exception: log.error("Query error:", exc_info=True) error_text = "Failed to query package index" error_details = traceback.format_exc() pkg = None else: pkg = pkgs[0] if pkg is None: error_text = "'{}' not was not found".format(name) if pkg: method_queued(self.add_package, (object,))(pkg) method_queued(dlg.accept, ())() else: method_queued(self.__show_error_for_query, (str, str)) \ (error_text, error_details) method_queued(dlg.reject, ())() f.add_done_callback(ondone) buttons.accepted.connect(query) buttons.rejected.connect(dlg.reject) dlg.exec_()
def variables_filter(model, parent=None, accepted_type=Orange.data.Variable, view_type=VariablesListItemView): """ GUI components: ListView with a lineedit which works as a filter. One can write a variable name in a edit box and possible matches are then shown in a listview. """ def update_completer_model(): """ This gets called when the model for available attributes changes through either drag/drop or the left/right button actions. """ nonlocal original_completer_items items = ["%s=%s" % item for v in model for item in v.attributes.items()] new = sorted(set(items)) if new != original_completer_items: original_completer_items = new completer_model.setStringList(original_completer_items) def update_completer_prefix(): """ Prefixes all items in the completer model with the current already done completion to enable the completion of multiple keywords. """ nonlocal original_completer_items prefix = str(completer.completionPrefix()) if not prefix.endswith(" ") and " " in prefix: prefix, _ = prefix.rsplit(" ", 1) items = [prefix + " " + item for item in original_completer_items] else: items = original_completer_items old = list(map(str, completer_model.stringList())) if set(old) != set(items): completer_model.setStringList(items) original_completer_items = [] filter_edit = QLineEdit() filter_edit.setToolTip("Filter the list of available variables.") filter_edit.setPlaceholderText("Filter") completer_model = QStringListModel() completer = QCompleter(completer_model, filter_edit) completer.setCompletionMode(QCompleter.InlineCompletion) completer.setModelSorting(QCompleter.CaseSensitivelySortedModel) filter_edit.setCompleter(completer) completer_navigator = CompleterNavigator(parent) filter_edit.installEventFilter(completer_navigator) proxy = VariableFilterProxyModel() proxy.setSourceModel(model) view = view_type(acceptedType=accepted_type) view.setModel(proxy) model.dataChanged.connect(update_completer_model) model.rowsInserted.connect(update_completer_model) model.rowsRemoved.connect(update_completer_model) filter_edit.textChanged.connect(update_completer_prefix) filter_edit.textChanged.connect(proxy.set_filter_string) return filter_edit, view
class ComboBoxSearch(QComboBox): """ A drop down list combo box with filter/search. The popup list view is filtered by text entered in the filter field. Note ---- `popup`, `lineEdit` and `completer` from the base QComboBox class are unused. Setting/modifying them will have no effect. """ # NOTE: Setting editable + QComboBox.NoInsert policy + ... did not achieve # the same results. def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__searchline = QLineEdit(self, visible=False, frame=False) self.__searchline.setAttribute(Qt.WA_MacShowFocusRect, False) self.__searchline.setFocusProxy(self) self.__popup = None # type: Optional[QAbstractItemModel] self.__proxy = None # type: Optional[QSortFilterProxyModel] self.__popupTimer = QElapsedTimer() self.setFocusPolicy(Qt.ClickFocus | Qt.TabFocus) def showPopup(self): # type: () -> None """ Reimplemented from QComboBox.showPopup Popup up a customized view and filter edit line. Note ---- The .popup(), .lineEdit(), .completer() of the base class are not used. """ if self.__popup is not None: # We have user entered state that cannot be disturbed # (entered filter text, scroll offset, ...) return # pragma: no cover if self.count() == 0: return opt = QStyleOptionComboBox() self.initStyleOption(opt) popup = QListView( uniformItemSizes=True, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAsNeeded, iconSize=self.iconSize(), ) popup.setFocusProxy(self.__searchline) popup.setParent(self, Qt.Popup | Qt.FramelessWindowHint) popup.setItemDelegate(_ComboBoxListDelegate(popup)) proxy = QSortFilterProxyModel(popup, filterCaseSensitivity=Qt.CaseInsensitive) proxy.setFilterKeyColumn(self.modelColumn()) proxy.setSourceModel(self.model()) popup.setModel(proxy) root = proxy.mapFromSource(self.rootModelIndex()) popup.setRootIndex(root) self.__popup = popup self.__proxy = proxy self.__searchline.setText("") self.__searchline.setPlaceholderText("Filter...") self.__searchline.setVisible(True) self.__searchline.textEdited.connect(proxy.setFilterFixedString) style = self.style() # type: QStyle popuprect_origin = style.subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxListBoxPopup, self) # type: QRect popuprect_origin = QRect(self.mapToGlobal(popuprect_origin.topLeft()), popuprect_origin.size()) editrect = style.subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self) # type: QRect self.__searchline.setGeometry(editrect) desktop = QApplication.desktop() screenrect = desktop.availableGeometry(self) # type: QRect # get the height for the view listrect = QRect() for i in range(min(proxy.rowCount(root), self.maxVisibleItems())): index = proxy.index(i, self.modelColumn(), root) if index.isValid(): listrect = listrect.united(popup.visualRect(index)) if listrect.height() >= screenrect.height(): break window = popup.window() # type: QWidget window.ensurePolished() if window.layout() is not None: window.layout().activate() else: QApplication.sendEvent(window, QEvent(QEvent.LayoutRequest)) margins = qwidget_margin_within(popup.viewport(), window) height = (listrect.height() + 2 * popup.spacing() + margins.top() + margins.bottom()) popup_size = (QSize(popuprect_origin.width(), height).expandedTo( window.minimumSize()).boundedTo(window.maximumSize()).boundedTo( screenrect.size())) popuprect = QRect(popuprect_origin.bottomLeft(), popup_size) popuprect = dropdown_popup_geometry(popuprect, popuprect_origin, screenrect) popup.setGeometry(popuprect) current = proxy.mapFromSource(self.model().index( self.currentIndex(), self.modelColumn(), self.rootModelIndex())) popup.setCurrentIndex(current) popup.scrollTo(current, QAbstractItemView.EnsureVisible) popup.show() popup.setFocus(Qt.PopupFocusReason) popup.installEventFilter(self) popup.viewport().installEventFilter(self) popup.viewport().setMouseTracking(True) self.update() self.__popupTimer.restart() def hidePopup(self): """Reimplemented""" if self.__popup is not None: popup = self.__popup self.__popup = self.__proxy = None popup.setFocusProxy(None) popup.hide() popup.deleteLater() popup.removeEventFilter(self) popup.viewport().removeEventFilter(self) # need to call base hidePopup even though the base showPopup was not # called (update internal state wrt. 'pressed' arrow, ...) super().hidePopup() self.__searchline.hide() self.update() def initStyleOption(self, option): # type: (QStyleOptionComboBox) -> None super().initStyleOption(option) option.editable = True def __updateGeometries(self): opt = QStyleOptionComboBox() self.initStyleOption(opt) editarea = self.style().subControlRect(QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self) self.__searchline.setGeometry(editarea) def resizeEvent(self, event): """Reimplemented.""" super().resizeEvent(event) self.__updateGeometries() def paintEvent(self, event): """Reimplemented.""" opt = QStyleOptionComboBox() self.initStyleOption(opt) painter = QStylePainter(self) painter.drawComplexControl(QStyle.CC_ComboBox, opt) if not self.__searchline.isVisibleTo(self): opt.editable = False painter.drawControl(QStyle.CE_ComboBoxLabel, opt) def eventFilter(self, obj, event): # pylint: disable=too-many-branches # type: (QObject, QEvent) -> bool """Reimplemented.""" etype = event.type() if etype == QEvent.FocusOut and self.__popup is not None: self.hidePopup() return True if etype == QEvent.Hide and self.__popup is not None: self.hidePopup() return False if etype == QEvent.KeyPress or etype == QEvent.KeyRelease or \ etype == QEvent.ShortcutOverride and obj is self.__popup: event = event # type: QKeyEvent key, modifiers = event.key(), event.modifiers() if key in (Qt.Key_Enter, Qt.Key_Return, Qt.Key_Select): current = self.__popup.currentIndex() if current.isValid(): self.__activateProxyIndex(current) elif key in (Qt.Key_Up, Qt.Key_Down, Qt.Key_PageUp, Qt.Key_PageDown): return False # elif key in (Qt.Key_Tab, Qt.Key_Backtab): pass elif key == Qt.Key_Escape or \ (key == Qt.Key_F4 and modifiers & Qt.AltModifier): self.__popup.hide() return True else: # pass the input events to the filter edit line (no propagation # up the parent chain). self.__searchline.event(event) if event.isAccepted(): return True if etype == QEvent.MouseButtonRelease and self.__popup is not None \ and obj is self.__popup.viewport() \ and self.__popupTimer.elapsed() >= \ QApplication.doubleClickInterval(): event = event # type: QMouseEvent index = self.__popup.indexAt(event.pos()) if index.isValid(): self.__activateProxyIndex(index) if etype == QEvent.MouseMove and self.__popup is not None \ and obj is self.__popup.viewport(): event = event # type: QMouseEvent opt = QStyleOptionComboBox() self.initStyleOption(opt) style = self.style() # type: QStyle if style.styleHint(QStyle.SH_ComboBox_ListMouseTracking, opt, self): index = self.__popup.indexAt(event.pos()) if index.isValid() and \ index.flags() & (Qt.ItemIsEnabled | Qt.ItemIsSelectable): self.__popup.setCurrentIndex(index) if etype == QEvent.MouseButtonPress and self.__popup is obj: # Popup border or out of window mouse button press/release. # At least on windows this needs to be handled. style = self.style() opt = QStyleOptionComboBox() self.initStyleOption(opt) opt.subControls = QStyle.SC_All opt.activeSubControls = QStyle.SC_ComboBoxArrow pos = self.mapFromGlobal(event.globalPos()) sc = style.hitTestComplexControl(QStyle.CC_ComboBox, opt, pos, self) if sc != QStyle.SC_None: self.__popup.setAttribute(Qt.WA_NoMouseReplay) self.hidePopup() return super().eventFilter(obj, event) def __activateProxyIndex(self, index): # type: (QModelIndex) -> None # Set current and activate the source index corresponding to the proxy # index in the popup's model. if self.__popup is not None and index.isValid(): proxy = self.__popup.model() assert index.model() is proxy index = proxy.mapToSource(index) assert index.model() is self.model() if index.isValid() and \ index.flags() & (Qt.ItemIsEnabled | Qt.ItemIsSelectable): self.hidePopup() text = self.itemText(index.row()) self.setCurrentIndex(index.row()) self.activated[int].emit(index.row()) self.activated[str].emit(text)
def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.var = None # type: Optional[Variable] layout = QVBoxLayout() self.setLayout(layout) self.form = form = QFormLayout( fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, objectName="editor-form-layout" ) layout.addLayout(self.form) self.name_edit = QLineEdit(objectName="name-editor") self.name_edit.editingFinished.connect( lambda: self.name_edit.isModified() and self.on_name_changed() ) form.addRow("Name:", self.name_edit) vlayout = QVBoxLayout(margin=0, spacing=1) self.labels_edit = view = QTreeView( objectName="annotation-pairs-edit", rootIsDecorated=False, editTriggers=QTreeView.DoubleClicked | QTreeView.EditKeyPressed, ) self.labels_model = model = DictItemsModel() view.setModel(model) view.selectionModel().selectionChanged.connect( self.on_label_selection_changed) agrp = QActionGroup(view, objectName="annotate-action-group") action_add = QAction( "+", self, objectName="action-add-label", toolTip="Add a new label.", shortcut=QKeySequence(QKeySequence.New), shortcutContext=Qt.WidgetShortcut ) action_delete = QAction( "\N{MINUS SIGN}", self, objectName="action-delete-label", toolTip="Remove selected label.", shortcut=QKeySequence(QKeySequence.Delete), shortcutContext=Qt.WidgetShortcut ) agrp.addAction(action_add) agrp.addAction(action_delete) view.addActions([action_add, action_delete]) def add_label(): row = [QStandardItem(), QStandardItem()] model.appendRow(row) idx = model.index(model.rowCount() - 1, 0) view.setCurrentIndex(idx) view.edit(idx) def remove_label(): rows = view.selectionModel().selectedRows(0) if rows: assert len(rows) == 1 idx = rows[0].row() model.removeRow(idx) action_add.triggered.connect(add_label) action_delete.triggered.connect(remove_label) agrp.setEnabled(False) self.add_label_action = action_add self.remove_label_action = action_delete # Necessary signals to know when the labels change model.dataChanged.connect(self.on_labels_changed) model.rowsInserted.connect(self.on_labels_changed) model.rowsRemoved.connect(self.on_labels_changed) vlayout.addWidget(self.labels_edit) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) button = FixedSizeButton( self, defaultAction=self.add_label_action, accessibleName="Add", ) hlayout.addWidget(button) button = FixedSizeButton( self, defaultAction=self.remove_label_action, accessibleName="Remove", ) hlayout.addWidget(button) hlayout.addStretch(10) vlayout.addLayout(hlayout) form.addRow("Labels:", vlayout)
class OWOracleSQL(OWWidget): name = "Oracle SQL" description = "Select data from oracle databases" icon = "icons/OracleSQL.svg" class Error(OWWidget.Error): no_backends = Msg( "Please install cx_Oracle package. It is either missing or not working properly" ) class Outputs: data = Output("Data", Table) autocommit = settings.Setting(False, schema_only=True) savedQuery = settings.Setting(None, schema_only=True) savedUsername = settings.Setting(None, schema_only=True) savedPwd = settings.Setting(None, schema_only=True) savedDB = settings.Setting(None, schema_only=True) def __init__(self): super().__init__() self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.domain = None self.data = None self.query = '' if self.savedQuery is not None: self.query = self.savedQuery self.username = '' if self.savedUsername is not None: self.username = self.savedUsername self.password = '' if self.savedPwd is not None: self.password = self.savedPwd self.database = '' if self.savedDB is not None: self.database = self.savedDB #Control Area layout self.connectBox = gui.widgetBox(self.controlArea, "Database connection") self.connectBox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) #Database self.userLabel = gui.label(self.connectBox, self, 'User name') self.connectUser = QLineEdit(self.username, self) self.connectBox.layout().addWidget(self.connectUser) self.passwordLabel = gui.label(self.connectBox, self, 'Password') self.connectPassword = QLineEdit(self.password, self) self.connectPassword.setEchoMode(QLineEdit.Password) self.connectBox.layout().addWidget(self.connectPassword) self.dbLabel = gui.label(self.connectBox, self, 'Database') self.connectDB = QLineEdit(self.database, self) self.connectBox.layout().addWidget(self.connectDB) self.runSQL = gui.auto_commit(self.connectBox, self, 'autocommit', label='Run SQL', commit=self.commit) # query self.sqlBox = gui.widgetBox(self.mainArea, "SQL") self.queryTextEdit = QPlainTextEdit(self.query, self) self.sqlBox.layout().addWidget(self.queryTextEdit) QTimer.singleShot(0, self.commit) def handleNewSignals(self): self._invalidate() def countUniques(self, lst): return len(set([x for x in lst if x is not None])) def setOfUniques(self, lst): return sorted(set([x for x in lst if x is not None])) def dateToStr(self, lst): return [str(x) if x is not None else x for x in lst] def commit(self): if cx_Oracle is None: data = [] columns = [] self.Error.no_backends() username = None password = None database = None query = None else: username = self.connectUser.text() password = self.connectPassword.text() database = self.connectDB.text() con = cx_Oracle.connect(username + "/" + password + "@" + database) query = self.queryTextEdit.toPlainText() cur = con.cursor() cur.execute(query) data = cur.fetchall() columns = [i[0] for i in cur.description] data_tr = list(zip(*data)) def create_variable(column_name, column_data): continuous_types = (int, float, type(None)) datetime_types = (datetime.datetime, type(None)) column_name = str(column_name) if all(isinstance(x, continuous_types) for x in column_data): return ContinuousVariable(column_name) if all(isinstance(x, datetime_types) for x in column_data): return TimeVariable(column_name) if self.countUniques(column_data) < 101: return DiscreteVariable(str(column_name), self.setOfUniques(column_data)) return DiscreteVariable(str(column_name), self.setOfUniques(column_data)) featurelist = [ create_variable(column_name, column_data) for column_name, column_data in zip(columns, data_tr) ] data_tr = [ self.dateToStr(data) if isinstance(var, TimeVariable) else data for (var, data) in zip(featurelist, data_tr) ] data = list(zip(*data_tr)) orangedomain = Domain(featurelist) orangetable = Table(orangedomain, data) self.Outputs.data.send(orangetable) self.savedQuery = query self.savedUsername = username self.savedPwd = password self.savedDB = database def _invalidate(self): self.commit()
def __init__(self): super().__init__() self.backend = None self.data_desc_table = None self.database_desc = None vbox = gui.vBox(self.controlArea, "Server", addSpace=True) box = gui.vBox(vbox) self.backends = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) if len(self.backends): self.backendcombo.setModel(self.backends) else: self.Error.no_backends() box.setEnabled(False) box.layout().addWidget(self.backendcombo) self.servertext = QLineEdit(box) self.servertext.setPlaceholderText('Server') self.servertext.setToolTip('Server') self.servertext.editingFinished.connect(self._load_credentials) if self.host: self.servertext.setText(self.host if not self.port else '{}:{}'.format(self.host, self.port)) box.layout().addWidget(self.servertext) self.databasetext = QLineEdit(box) self.databasetext.setPlaceholderText('Database[/Schema]') self.databasetext.setToolTip('Database or optionally Database/Schema') if self.database: self.databasetext.setText( self.database if not self.schema else '{}/{}'.format(self.database, self.schema)) box.layout().addWidget(self.databasetext) self.usernametext = QLineEdit(box) self.usernametext.setPlaceholderText('Username') self.usernametext.setToolTip('Username') box.layout().addWidget(self.usernametext) self.passwordtext = QLineEdit(box) self.passwordtext.setPlaceholderText('Password') self.passwordtext.setToolTip('Password') self.passwordtext.setEchoMode(QLineEdit.Password) box.layout().addWidget(self.passwordtext) self._load_credentials() self.tables = TableModel() tables = gui.hBox(box) self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength ) self.tablecombo.setModel(self.tables) self.tablecombo.setToolTip('table') tables.layout().addWidget(self.tablecombo) self.connect() index = self.tablecombo.findText(str(self.table)) if index != -1: self.tablecombo.setCurrentIndex(index) # set up the callback to select_table in case of selection change self.tablecombo.activated[int].connect(self.select_table) self.connectbutton = gui.button( tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) tables.layout().addWidget(self.connectbutton) self.custom_sql = gui.vBox(box) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) self.custom_sql.layout().addWidget(self.sqltext) mt = gui.hBox(self.custom_sql) cb = gui.checkBox(mt, self, 'materialize', 'Materialize to table ') cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') self.executebtn = gui.button( self.custom_sql, self, 'Execute', callback=self.open_table) box.layout().addWidget(self.custom_sql) gui.checkBox(box, self, "guess_values", "Auto-discover categorical variables", callback=self.open_table) gui.checkBox(box, self, "download", "Download data to local memory", callback=self.open_table) gui.rubber(self.buttonsArea) QTimer.singleShot(0, self.select_table)
class OWCreateInstance(OWWidget): name = "Create Instance" description = "Interactively create a data instance from sample dataset." icon = "icons/CreateInstance.svg" category = "Data" keywords = ["simulator"] priority = 4000 class Inputs: data = Input("Data", Table) reference = Input("Reference", Table) class Outputs: data = Output("Data", Table) class Information(OWWidget.Information): nans_removed = Msg("Variables with only missing values were " "removed from the list.") want_main_area = False ACTIONS = ["median", "mean", "random", "input"] HEADER = [["name", "Variable"], ["variable", "Value"]] Header = namedtuple("header", [tag for tag, _ in HEADER])(*range(len(HEADER))) values: Dict[str, Union[float, str]] = Setting({}, schema_only=True) append_to_data = Setting(True) auto_commit = Setting(True) def __init__(self): super().__init__() self.data: Optional[Table] = None self.reference: Optional[Table] = None self.filter_edit = QLineEdit(textChanged=self.__filter_edit_changed, placeholderText="Filter...") self.view = QTableView(sortingEnabled=True, contextMenuPolicy=Qt.CustomContextMenu, selectionMode=QTableView.NoSelection) self.view.customContextMenuRequested.connect(self.__menu_requested) self.view.setItemDelegateForColumn(self.Header.variable, VariableDelegate(self)) self.view.verticalHeader().hide() self.view.horizontalHeader().setStretchLastSection(True) self.view.horizontalHeader().setMaximumSectionSize(350) self.model = VariableItemModel(self) self.model.setHorizontalHeaderLabels([x for _, x in self.HEADER]) self.model.dataChanged.connect(self.__table_data_changed) self.model.dataHasNanColumn.connect(self.Information.nans_removed) self.proxy_model = QSortFilterProxyModel() self.proxy_model.setFilterKeyColumn(-1) self.proxy_model.setFilterCaseSensitivity(False) self.proxy_model.setSourceModel(self.model) self.view.setModel(self.proxy_model) vbox = gui.vBox(self.controlArea, box=True) vbox.layout().addWidget(self.filter_edit) vbox.layout().addWidget(self.view) box = gui.hBox(vbox, objectName="buttonBox") gui.rubber(box) for name in self.ACTIONS: gui.button(box, self, name.capitalize(), lambda *args, fun=name: self._initialize_values(fun), autoDefault=False) gui.rubber(box) box = gui.auto_apply(self.controlArea, self, "auto_commit") box.button.setFixedWidth(180) box.layout().insertStretch(0) # pylint: disable=unnecessary-lambda append = gui.checkBox(None, self, "append_to_data", "Append this instance to input data", callback=lambda: self.commit()) box.layout().insertWidget(0, append) self._set_input_summary() self._set_output_summary() self.settingsAboutToBePacked.connect(self.pack_settings) def __filter_edit_changed(self): self.proxy_model.setFilterFixedString(self.filter_edit.text().strip()) def __table_data_changed(self): self.commit() def __menu_requested(self, point: QPoint): index = self.view.indexAt(point) model: QSortFilterProxyModel = index.model() source_index = model.mapToSource(index) menu = QMenu(self) for action in self._create_actions(source_index): menu.addAction(action) menu.popup(self.view.viewport().mapToGlobal(point)) def _create_actions(self, index: QModelIndex) -> List[QAction]: actions = [] for name in self.ACTIONS: action = QAction(name.capitalize(), self) action.triggered.connect( lambda *args, fun=name: self._initialize_values(fun, [index])) actions.append(action) return actions def _initialize_values(self, fun: str, indices: List[QModelIndex] = None): cont_fun = { "median": np.nanmedian, "mean": np.nanmean, "random": cont_random, "input": np.nanmean }.get(fun, NotImplemented) disc_fun = { "median": majority, "mean": majority, "random": disc_random, "input": majority }.get(fun, NotImplemented) if not self.data or fun == "input" and not self.reference: return self.model.dataChanged.disconnect(self.__table_data_changed) rows = range(self.proxy_model.rowCount()) if indices is None else \ [index.row() for index in indices] for row in rows: index = self.model.index(row, self.Header.variable) variable = self.model.data(index, VariableRole) if fun == "input": if variable not in self.reference.domain: continue values = self.reference.get_column_view(variable)[0] if variable.is_primitive(): values = values.astype(float) if all(np.isnan(values)): continue else: values = self.model.data(index, ValuesRole) if variable.is_continuous: value = cont_fun(values) value = round(value, variable.number_of_decimals) elif variable.is_discrete: value = disc_fun(values) elif variable.is_string: value = "" else: raise NotImplementedError self.model.setData(index, value, ValueRole) self.model.dataChanged.connect(self.__table_data_changed) self.commit() @Inputs.data def set_data(self, data: Table): self.data = data self._set_input_summary() self._set_model_data() self.unconditional_commit() def _set_model_data(self): self.Information.nans_removed.clear() self.model.removeRows(0, self.model.rowCount()) if not self.data: return self.model.set_data(self.data, self.values) self.values = {} self.view.horizontalHeader().setStretchLastSection(False) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self.view.horizontalHeader().setStretchLastSection(True) @Inputs.reference def set_reference(self, data: Table): self.reference = data self._set_input_summary() def _set_input_summary(self): n_data = len(self.data) if self.data else 0 n_refs = len(self.reference) if self.reference else 0 summary, details, kwargs = self.info.NoInput, "", {} if self.data or self.reference: summary = f"{self.info.format_number(n_data)}, " \ f"{self.info.format_number(n_refs)}" data_list = [("Data", self.data), ("Reference", self.reference)] details = format_multiple_summaries(data_list) kwargs = {"format": Qt.RichText} self.info.set_input_summary(summary, details, **kwargs) def _set_output_summary(self, data: Optional[Table] = None): if data: summary, details = len(data), format_summary_details(data) else: summary, details = self.info.NoOutput, "" self.info.set_output_summary(summary, details) def commit(self): output_data = None if self.data: output_data = self._create_data_from_values() if self.append_to_data: output_data = self._append_to_data(output_data) self._set_output_summary(output_data) self.Outputs.data.send(output_data) def _create_data_from_values(self) -> Table: data = Table.from_domain(self.data.domain, 1) data.name = "created" data.X[:] = np.nan data.Y[:] = np.nan for i, m in enumerate(self.data.domain.metas): data.metas[:, i] = "" if m.is_string else np.nan values = self._get_values() for var_name, value in values.items(): data[:, var_name] = value return data def _append_to_data(self, data: Table) -> Table: assert self.data assert len(data) == 1 var = DiscreteVariable("Source ID", values=(self.data.name, data.name)) data = Table.concatenate([self.data, data], axis=0) domain = Domain(data.domain.attributes, data.domain.class_vars, data.domain.metas + (var, )) data = data.transform(domain) data.metas[:len(self.data), -1] = 0 data.metas[len(self.data):, -1] = 1 return data def _get_values(self) -> Dict[str, Union[str, float]]: values = {} for row in range(self.model.rowCount()): index = self.model.index(row, self.Header.variable) values[self.model.data(index, VariableRole).name] = \ self.model.data(index, ValueRole) return values def send_report(self): if not self.data: return self.report_domain("Input", self.data.domain) self.report_domain("Output", self.data.domain) items = [] values: Dict = self._get_values() for var in self.data.domain.variables + self.data.domain.metas: val = values.get(var.name, np.nan) if var.is_primitive(): val = var.repr_val(val) items.append([f"{var.name}:", val]) self.report_table("Values", items) @staticmethod def sizeHint(): return QSize(600, 500) def pack_settings(self): self.values: Dict[str, Union[str, float]] = self._get_values()
class SelectionSetsWidget(QFrame): """ Widget for managing multiple stored item selections """ selectionModified = Signal(bool) def __init__(self, parent): QFrame.__init__(self, parent) self.setContentsMargins(0, 0, 0, 0) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(1) self._setNameLineEdit = QLineEdit(self) layout.addWidget(self._setNameLineEdit) self._setListView = QListView(self) self._listModel = QStandardItemModel(self) self._proxyModel = QSortFilterProxyModel(self) self._proxyModel.setSourceModel(self._listModel) self._setListView.setModel(self._proxyModel) self._setListView.setItemDelegate(ListItemDelegate(self)) self._setNameLineEdit.textChanged.connect( self._proxyModel.setFilterFixedString) self._completer = QCompleter(self._listModel, self) self._setNameLineEdit.setCompleter(self._completer) self._listModel.itemChanged.connect(self._onSetNameChange) layout.addWidget(self._setListView) buttonLayout = QHBoxLayout() self._addAction = QAction( "+", self, toolTip="Add a new sort key") self._updateAction = QAction( "Update", self, toolTip="Update/save current selection") self._removeAction = QAction( "\u2212", self, toolTip="Remove selected sort key.") self._addToolButton = QToolButton(self) self._updateToolButton = QToolButton(self) self._removeToolButton = QToolButton(self) self._updateToolButton.setSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self._addToolButton.setDefaultAction(self._addAction) self._updateToolButton.setDefaultAction(self._updateAction) self._removeToolButton.setDefaultAction(self._removeAction) buttonLayout.addWidget(self._addToolButton) buttonLayout.addWidget(self._updateToolButton) buttonLayout.addWidget(self._removeToolButton) layout.addLayout(buttonLayout) self.setLayout(layout) self._addAction.triggered.connect(self.addCurrentSelection) self._updateAction.triggered.connect(self.updateSelectedSelection) self._removeAction.triggered.connect(self.removeSelectedSelection) self._setListView.selectionModel().selectionChanged.connect( self._onListViewSelectionChanged) self.selectionModel = None self._selections = [] def sizeHint(self): size = QFrame.sizeHint(self) return QSize(size.width(), 150) def _onSelectionChanged(self, selected, deselected): self.setSelectionModified(True) def _onListViewSelectionChanged(self, selected, deselected): try: index = self._setListView.selectedIndexes()[0] except IndexError: return self.commitSelection(self._proxyModel.mapToSource(index).row()) def _onSetNameChange(self, item): self.selections[item.row()].name = str(item.text()) def _setButtonStates(self, val): self._updateToolButton.setEnabled(val) def setSelectionModel(self, selectionModel): if self.selectionModel: self.selectionModel.selectionChanged.disconnect( self._onSelectionChanged) self.selectionModel = selectionModel self.selectionModel.selectionChanged.connect(self._onSelectionChanged) def addCurrentSelection(self): item = self.addSelection( SelectionByKey(self.selectionModel.selection(), name="New selection", key=(1, 2, 3, 10))) index = self._proxyModel.mapFromSource(item.index()) self._setListView.setCurrentIndex(index) self._setListView.edit(index) self.setSelectionModified(False) def removeSelectedSelection(self): i = self._proxyModel.mapToSource(self._setListView.currentIndex()).row() self._listModel.takeRow(i) del self.selections[i] def updateCurentSelection(self): i = self._proxyModel.mapToSource(self._setListView.selectedIndex()).row() self.selections[i].setSelection(self.selectionModel.selection()) self.setSelectionModified(False) def addSelection(self, selection, name=""): self._selections.append(selection) item = QStandardItem(selection.name) item.setFlags(item.flags() ^ Qt.ItemIsDropEnabled) self._listModel.appendRow(item) self.setSelectionModified(False) return item def updateSelectedSelection(self): i = self._proxyModel.mapToSource(self._setListView.currentIndex()).row() self.selections[i].setSelection(self.selectionModel.selection()) self.setSelectionModified(False) def setSelectionModified(self, val): self._selectionModified = val self._setButtonStates(val) self.selectionModified.emit(bool(val)) def commitSelection(self, index): selection = self.selections[index] selection.select(self.selectionModel) def setSelections(self, selections): self._listModel.clear() for selection in selections: self.addSelection(selection) def selections(self): return self._selections selections = property(selections, setSelections)
def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.var = None # type: Optional[Variable] layout = QVBoxLayout() self.setLayout(layout) self.form = form = QFormLayout( fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, objectName="editor-form-layout") layout.addLayout(self.form) self.name_edit = QLineEdit(objectName="name-editor") self.name_edit.editingFinished.connect( lambda: self.name_edit.isModified() and self.on_name_changed()) form.addRow("Name:", self.name_edit) vlayout = QVBoxLayout(margin=0, spacing=1) self.labels_edit = view = QTreeView( objectName="annotation-pairs-edit", rootIsDecorated=False, editTriggers=QTreeView.DoubleClicked | QTreeView.EditKeyPressed, ) self.labels_model = model = DictItemsModel() view.setModel(model) view.selectionModel().selectionChanged.connect( self.on_label_selection_changed) agrp = QActionGroup(view, objectName="annotate-action-group") action_add = QAction("+", self, objectName="action-add-label", toolTip="Add a new label.", shortcut=QKeySequence(QKeySequence.New), shortcutContext=Qt.WidgetShortcut) action_delete = QAction("\N{MINUS SIGN}", self, objectName="action-delete-label", toolTip="Remove selected label.", shortcut=QKeySequence(QKeySequence.Delete), shortcutContext=Qt.WidgetShortcut) agrp.addAction(action_add) agrp.addAction(action_delete) view.addActions([action_add, action_delete]) def add_label(): row = [QStandardItem(), QStandardItem()] model.appendRow(row) idx = model.index(model.rowCount() - 1, 0) view.setCurrentIndex(idx) view.edit(idx) def remove_label(): rows = view.selectionModel().selectedRows(0) if rows: assert len(rows) == 1 idx = rows[0].row() model.removeRow(idx) action_add.triggered.connect(add_label) action_delete.triggered.connect(remove_label) agrp.setEnabled(False) self.add_label_action = action_add self.remove_label_action = action_delete # Necessary signals to know when the labels change model.dataChanged.connect(self.on_labels_changed) model.rowsInserted.connect(self.on_labels_changed) model.rowsRemoved.connect(self.on_labels_changed) vlayout.addWidget(self.labels_edit) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) button = FixedSizeButton( self, defaultAction=self.add_label_action, accessibleName="Add", ) hlayout.addWidget(button) button = FixedSizeButton( self, defaultAction=self.remove_label_action, accessibleName="Remove", ) hlayout.addWidget(button) hlayout.addStretch(10) vlayout.addLayout(hlayout) form.addRow("Labels:", vlayout)
def _setup_gui(self): grid = QGridLayout() box = gui.widgetBox(self.controlArea, "Scoring Methods", grid) yake_cb = gui.comboBox( self.controlArea, self, "yake_lang_index", items=YAKE_LANGUAGES, callback=self.__on_yake_lang_changed ) rake_cb = gui.comboBox( self.controlArea, self, "rake_lang_index", items=RAKE_LANGUAGES, callback=self.__on_rake_lang_changed ) embedding_cb = gui.comboBox( self.controlArea, self, "embedding_lang_index", items=EMBEDDING_LANGUAGES, callback=self.__on_emb_lang_changed ) for i, (method_name, _) in enumerate(ScoringMethods.ITEMS): check_box = QCheckBox(method_name, self) check_box.setChecked(method_name in self.selected_scoring_methods) check_box.stateChanged.connect( lambda state, name=method_name: self.__on_scoring_method_state_changed(state, name) ) box.layout().addWidget(check_box, i, 0) if method_name == ScoringMethods.YAKE: box.layout().addWidget(yake_cb, i, 1) if method_name == ScoringMethods.RAKE: box.layout().addWidget(rake_cb, i, 1) if method_name == ScoringMethods.EMBEDDING: box.layout().addWidget(embedding_cb, i, 1) box = gui.vBox(self.controlArea, "Aggregation") gui.comboBox( box, self, "agg_method", items=AggregationMethods.ITEMS, callback=self.update_scores ) box = gui.vBox(self.buttonsArea, "Select Words") grid = QGridLayout() grid.setContentsMargins(0, 0, 0, 0) box.layout().addLayout(grid) self.__sel_method_buttons = QButtonGroup() for method, label in enumerate(SelectionMethods.ITEMS): button = QRadioButton(label) button.setChecked(method == self.sel_method) grid.addWidget(button, method, 0) self.__sel_method_buttons.addButton(button, method) self.__sel_method_buttons.buttonClicked[int].connect( self._set_selection_method ) spin = gui.spin( box, self, "n_selected", 1, 999, addToLayout=False, callback=lambda: self._set_selection_method( SelectionMethods.N_BEST) ) grid.addWidget(spin, 3, 1) gui.rubber(self.controlArea) gui.auto_send(self.buttonsArea, self, "auto_apply") self.__filter_line_edit = QLineEdit( textChanged=self.__on_filter_changed, placeholderText="Filter..." ) self.mainArea.layout().addWidget(self.__filter_line_edit) def select_manual(): self._set_selection_method(SelectionMethods.MANUAL) self.view = KeywordsTableView() self.view.pressedAny.connect(select_manual) self.view.horizontalHeader().setSortIndicator(*self.DEFAULT_SORTING) self.view.horizontalHeader().sectionClicked.connect( self.__on_horizontal_header_clicked) self.mainArea.layout().addWidget(self.view) proxy = SortFilterProxyModel() proxy.setFilterKeyColumn(0) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) self.view.model().setSourceModel(self.model) self.view.selectionModel().selectionChanged.connect( self.__on_selection_changed )
class VariableEditor(QWidget): """An editor widget for a variable. Can edit the variable name, and its attributes dictionary. """ variable_changed = Signal() def __init__(self, parent=None): QWidget.__init__(self, parent) self.var = None self.setup_gui() def setup_gui(self): layout = QVBoxLayout() self.setLayout(layout) self.main_form = QFormLayout() self.main_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) layout.addLayout(self.main_form) self._setup_gui_name() self._setup_gui_labels() def _setup_gui_name(self): self.name_edit = QLineEdit() self.main_form.addRow("Name:", self.name_edit) self.name_edit.editingFinished.connect(self.on_name_changed) def _setup_gui_labels(self): vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) vlayout.setSpacing(1) self.labels_edit = QTreeView() self.labels_edit.setEditTriggers(QTreeView.CurrentChanged) self.labels_edit.setRootIsDecorated(False) self.labels_model = DictItemsModel() self.labels_edit.setModel(self.labels_model) self.labels_edit.selectionModel().selectionChanged.connect( self.on_label_selection_changed) # Necessary signals to know when the labels change self.labels_model.dataChanged.connect(self.on_labels_changed) self.labels_model.rowsInserted.connect(self.on_labels_changed) self.labels_model.rowsRemoved.connect(self.on_labels_changed) vlayout.addWidget(self.labels_edit) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.setSpacing(1) self.add_label_action = QAction( "+", self, toolTip="Add a new label.", triggered=self.on_add_label, enabled=False, shortcut=QKeySequence(QKeySequence.New)) self.remove_label_action = QAction( unicodedata.lookup("MINUS SIGN"), self, toolTip="Remove selected label.", triggered=self.on_remove_label, enabled=False, shortcut=QKeySequence(QKeySequence.Delete)) button_size = gui.toolButtonSizeHint() button_size = QSize(button_size, button_size) button = QToolButton(self) button.setFixedSize(button_size) button.setDefaultAction(self.add_label_action) hlayout.addWidget(button) button = QToolButton(self) button.setFixedSize(button_size) button.setDefaultAction(self.remove_label_action) hlayout.addWidget(button) hlayout.addStretch(10) vlayout.addLayout(hlayout) self.main_form.addRow("Labels:", vlayout) def set_data(self, var): """Set the variable to edit. """ self.clear() self.var = var if var is not None: self.name_edit.setText(var.name) self.labels_model.set_dict(dict(var.attributes)) self.add_label_action.setEnabled(True) else: self.add_label_action.setEnabled(False) self.remove_label_action.setEnabled(False) def get_data(self): """Retrieve the modified variable. """ name = str(self.name_edit.text()) labels = self.labels_model.get_dict() # Is the variable actually changed. if self.var is not None and not self.is_same(): var = type(self.var)(name) var.attributes.update(labels) self.var = var else: var = self.var return var def is_same(self): """Is the current model state the same as the input. """ name = str(self.name_edit.text()) labels = self.labels_model.get_dict() return (self.var is not None and name == self.var.name and labels == self.var.attributes) def clear(self): """Clear the editor state. """ self.var = None self.name_edit.setText("") self.labels_model.set_dict({}) def maybe_commit(self): if not self.is_same(): self.commit() def commit(self): """Emit a ``variable_changed()`` signal. """ self.variable_changed.emit() @Slot() def on_name_changed(self): self.maybe_commit() @Slot() def on_labels_changed(self, *args): self.maybe_commit() @Slot() def on_add_label(self): self.labels_model.appendRow([QStandardItem(""), QStandardItem("")]) row = self.labels_model.rowCount() - 1 index = self.labels_model.index(row, 0) self.labels_edit.edit(index) @Slot() def on_remove_label(self): rows = self.labels_edit.selectionModel().selectedRows() if rows: row = rows[0] self.labels_model.removeRow(row.row()) @Slot() def on_label_selection_changed(self): selected = self.labels_edit.selectionModel().selectedRows() self.remove_label_action.setEnabled(bool(len(selected)))
def __init__(self): super().__init__() self.allinfo_local = {} self.allinfo_remote = {} self.local_cache_path = os.path.join(data_dir(), self.DATASET_DIR) # current_output does not equal selected_id when, for instance, the # data is still downloading self.current_output = None self._header_labels = [ header['label'] for _, header in self.HEADER_SCHEMA ] self._header_index = namedtuple( '_header_index', [info_tag for info_tag, _ in self.HEADER_SCHEMA]) self.Header = self._header_index( *[index for index, _ in enumerate(self._header_labels)]) self.__awaiting_state = None # type: Optional[_FetchState] self.filterLineEdit = QLineEdit( textChanged=self.filter, placeholderText="Search for data set ...") self.mainArea.layout().addWidget(self.filterLineEdit) self.splitter = QSplitter(orientation=Qt.Vertical) self.view = TreeViewWithReturn( sortingEnabled=True, selectionMode=QTreeView.SingleSelection, alternatingRowColors=True, rootIsDecorated=False, editTriggers=QTreeView.NoEditTriggers, uniformRowHeights=True, toolTip="Press Return or double-click to send") # the method doesn't exists yet, pylint: disable=unnecessary-lambda self.view.doubleClicked.connect(self.commit) self.view.returnPressed.connect(self.commit) box = gui.widgetBox(self.splitter, "Description", addToLayout=False) self.descriptionlabel = QLabel( wordWrap=True, textFormat=Qt.RichText, ) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse)) self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) self.splitter.addWidget(self.view) self.splitter.addWidget(box) self.splitter.setSizes([300, 200]) self.splitter.splitterMoved.connect(lambda: setattr( self, "splitter_state", bytes(self.splitter.saveState()))) self.mainArea.layout().addWidget(self.splitter) proxy = QSortFilterProxyModel() proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) if self.splitter_state: self.splitter.restoreState(self.splitter_state) self.assign_delegates() self.setBlocking(True) self.setStatusMessage("Initializing") self._executor = ThreadPoolExecutor(max_workers=1) f = self._executor.submit(self.list_remote) w = FutureWatcher(f, parent=self) w.done.connect(self.__set_index)
class OWScoreDocuments(OWWidget, ConcurrentWidgetMixin): name = "Score Documents" description = "" icon = "icons/ScoreDocuments.svg" priority = 500 buttons_area_orientation = Qt.Vertical # default order - table sorted in input order DEFAULT_SORTING = (-1, Qt.AscendingOrder) settingsHandler = PerfectDomainContextHandler() auto_commit: bool = Setting(True) aggregation: int = Setting(0) word_frequency: bool = Setting(True) word_appearance: bool = Setting(False) embedding_similarity: bool = Setting(False) embedding_language: int = Setting(0) sort_column_order: Tuple[int, int] = Setting(DEFAULT_SORTING) selected_rows: List[int] = ContextSetting([], schema_only=True) sel_method: int = ContextSetting(SelectionMethods.N_BEST) n_selected: int = ContextSetting(3) class Inputs: corpus = Input("Corpus", Corpus) words = Input("Words", Table) class Outputs: selected_documents = Output("Selected documents", Corpus, default=True) corpus = Output("Corpus", Corpus) class Warning(OWWidget.Warning): corpus_not_normalized = Msg("Use Preprocess Text to normalize corpus.") class Error(OWWidget.Error): custom_err = Msg("{}") def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self._setup_control_area() self._setup_main_area() self.corpus = None self.words = None # saves scores avoid multiple computation of the same score self.scores = {} def _setup_control_area(self) -> None: box = gui.widgetBox(self.controlArea, "Word Scoring Methods") for value, (n, _, tt) in SCORING_METHODS.items(): b = gui.hBox(box, margin=0) gui.checkBox( b, self, value, label=n, callback=self.__setting_changed, tooltip=tt, ) if value in ADDITIONAL_OPTIONS: value, options = ADDITIONAL_OPTIONS[value] gui.comboBox( b, self, value, items=options, callback=self.__setting_changed, ) box = gui.widgetBox(self.controlArea, "Aggregation") gui.comboBox( box, self, "aggregation", items=[n for n in AGGREGATIONS], callback=self.__setting_changed, ) gui.rubber(self.controlArea) # select words box box = gui.vBox(self.buttonsArea, "Select Documents") grid = QGridLayout() grid.setContentsMargins(0, 0, 0, 0) self._sel_method_buttons = QButtonGroup() for method, label in enumerate(SelectionMethods.ITEMS): button = QRadioButton(label) button.setChecked(method == self.sel_method) grid.addWidget(button, method, 0) self._sel_method_buttons.addButton(button, method) self._sel_method_buttons.buttonClicked[int].connect( self.__set_selection_method) spin = gui.spin( box, self, "n_selected", 1, 999, addToLayout=False, callback=lambda: self.__set_selection_method(SelectionMethods. N_BEST), ) grid.addWidget(spin, 3, 1) box.layout().addLayout(grid) # autocommit gui.auto_send(self.buttonsArea, self, "auto_commit") def _setup_main_area(self) -> None: self._filter_line_edit = QLineEdit( textChanged=self.__on_filter_changed, placeholderText="Filter...") self.mainArea.layout().addWidget(self._filter_line_edit) self.model = model = ScoreDocumentsTableModel(parent=self) model.setHorizontalHeaderLabels(["Document"]) def select_manual(): self.__set_selection_method(SelectionMethods.MANUAL) self.view = view = ScoreDocumentsTableView() view.pressedAny.connect(select_manual) self.mainArea.layout().addWidget(view) # by default data are sorted in the Table order header = self.view.horizontalHeader() header.sectionClicked.connect(self.__on_horizontal_header_clicked) proxy_model = ScoreDocumentsProxyModel() proxy_model.setFilterKeyColumn(0) proxy_model.setFilterCaseSensitivity(False) view.setModel(proxy_model) view.model().setSourceModel(self.model) self.view.selectionModel().selectionChanged.connect( self.__on_selection_change) def __on_filter_changed(self) -> None: model = self.view.model() model.setFilterFixedString(self._filter_line_edit.text().strip()) def __on_horizontal_header_clicked(self, index: int): header = self.view.horizontalHeader() self.sort_column_order = (index, header.sortIndicatorOrder()) self._select_rows() # when sorting change output table must consider the new order # call explicitly since selection in table is not changed if (self.sel_method == SelectionMethods.MANUAL and self.selected_rows or self.sel_method == SelectionMethods.ALL): # retrieve selection in new order self.selected_rows = self.get_selected_indices() self._send_output() def __on_selection_change(self): self.selected_rows = self.get_selected_indices() self._send_output() def __set_selection_method(self, method: int): self.sel_method = method self._sel_method_buttons.button(method).setChecked(True) self._select_rows() @Inputs.corpus def set_data(self, corpus: Corpus) -> None: self.closeContext() self.Warning.corpus_not_normalized.clear() if corpus is None: self.corpus = None self._clear_and_run() return if not self._is_corpus_normalized(corpus): self.Warning.corpus_not_normalized() self.corpus = corpus self.selected_rows = [] self.openContext(corpus) self._sel_method_buttons.button(self.sel_method).setChecked(True) self._clear_and_run() @staticmethod def _get_word_attribute(words: Table) -> None: attrs = [ a for a in words.domain.metas + words.domain.variables if isinstance(a, StringVariable) ] if not attrs: return None words_attr = next( (a for a in attrs if a.attributes.get("type", "") == "words"), None) if words_attr: return words.get_column_view(words_attr)[0].tolist() else: # find the most suitable attribute - one with lowest average text # length - counted as a number of words def avg_len(attr): array_ = words.get_column_view(attr)[0] array_ = array_[~isnull(array_)] return sum(len(a.split()) for a in array_) / len(array_) attr = sorted(attrs, key=avg_len)[0] return words.get_column_view(attr)[0].tolist() @Inputs.words def set_words(self, words: Table) -> None: if words is None or len(words.domain.variables + words.domain.metas) == 0: self.words = None else: self.words = self._get_word_attribute(words) self._clear_and_run() def _gather_scores(self) -> Tuple[np.ndarray, List[str]]: """ Gather scores and labels for the dictionary that holds scores Returns ------- scores Scores table labels The list with score names for the header and variables names """ if self.corpus is None: return np.empty((0, 0)), [] aggregation = self._get_active_aggregation() scorers = self._get_active_scorers() methods = [m for m in scorers if (m, aggregation) in self.scores] scores = [self.scores[(m, aggregation)] for m in methods] scores = np.column_stack(scores) if scores else np.empty( (len(self.corpus), 0)) labels = [SCORING_METHODS[m][0] for m in methods] return scores, labels def _send_output(self) -> None: """ Create corpus with scores and output it """ if self.corpus is None: self.Outputs.corpus.send(None) self.Outputs.selected_documents.send(None) return scores, labels = self._gather_scores() if labels: d = self.corpus.domain domain = Domain( d.attributes, d.class_var, metas=d.metas + tuple( ContinuousVariable(get_unique_names(d, l)) for l in labels), ) out_corpus = Corpus.from_numpy( domain, self.corpus.X, self.corpus.Y, np.hstack([self.corpus.metas, scores]), ) Corpus.retain_preprocessing(self.corpus, out_corpus) else: out_corpus = self.corpus self.Outputs.corpus.send( create_annotated_table(out_corpus, self.selected_rows)) self.Outputs.selected_documents.send( out_corpus[self.selected_rows] if self.selected_rows else None) def _fill_table(self) -> None: """ Fill the table in the widget with scores and document names """ if self.corpus is None: self.model.clear() return scores, labels = self._gather_scores() labels = ["Document"] + labels titles = self.corpus.titles.tolist() # clearing selection and sorting to prevent SEGFAULT on model.wrap self.view.horizontalHeader().setSortIndicator(-1, Qt.AscendingOrder) with disconnected(self.view.selectionModel().selectionChanged, self.__on_selection_change): self.view.clearSelection() self.model.fill_table(titles, scores) self.model.setHorizontalHeaderLabels(labels) self.view.update_column_widths() if self.model.columnCount() > self.sort_column_order[0]: # if not enough columns do not apply sorting from settings since # sorting can besaved for score column while scores are still computing # tables is filled before scores are computed with document names self.view.horizontalHeader().setSortIndicator( *self.sort_column_order) self._select_rows() def _fill_and_output(self) -> None: """Fill the table in the widget and send the output""" self._fill_table() self._send_output() def _clear_and_run(self) -> None: """Clear cached scores and commit""" self.scores = {} self.cancel() self._fill_and_output() self.commit.now() def __setting_changed(self) -> None: self.commit.deferred() @gui.deferred def commit(self) -> None: self.Error.custom_err.clear() self.cancel() if self.corpus is not None and self.words is not None: scorers = self._get_active_scorers() aggregation = self._get_active_aggregation() new_scores = [ s for s in scorers if (s, aggregation) not in self.scores ] if new_scores: self.start( _run, self.corpus, self.words, new_scores, aggregation, { v: items[getattr(self, v)] for v, items in ADDITIONAL_OPTIONS.values() }, ) else: self._fill_and_output() def on_done(self, _: None) -> None: self._send_output() def on_partial_result(self, result: Tuple[str, str, np.ndarray]) -> None: sc_method, aggregation, scores = result self.scores[(sc_method, aggregation)] = scores self._fill_table() def on_exception(self, ex: Exception) -> None: self.Error.custom_err(ex) self._fill_and_output() def _get_active_scorers(self) -> List[str]: """ Gather currently active/selected scores Returns ------- List with selected scores names """ return [attr for attr in SCORING_METHODS if getattr(self, attr)] def _get_active_aggregation(self) -> str: """ Gather currently active/selected aggregation Returns ------- Selected aggregation name """ return list(AGGREGATIONS.keys())[self.aggregation] @staticmethod def _is_corpus_normalized(corpus: Corpus) -> bool: """ Check if corpus is normalized. """ return any( isinstance(pp, BaseNormalizer) for pp in corpus.used_preprocessor.preprocessors) def get_selected_indices(self) -> List[int]: # get indices in table's order - that the selected output table have same order selected_rows = sorted(self.view.selectionModel().selectedRows(), key=lambda idx: idx.row()) return [self.view.model().mapToSource(r).row() for r in selected_rows] def _select_rows(self): proxy_model = self.view.model() n_rows, n_columns = proxy_model.rowCount(), proxy_model.columnCount() if self.sel_method == SelectionMethods.NONE: selection = QItemSelection() elif self.sel_method == SelectionMethods.ALL: selection = QItemSelection( proxy_model.index(0, 0), proxy_model.index(n_rows - 1, n_columns - 1)) elif self.sel_method == SelectionMethods.MANUAL: selection = QItemSelection() new_sel = [] for row in self.selected_rows: if row < n_rows: new_sel.append(row) _selection = QItemSelection( self.model.index(row, 0), self.model.index(row, n_columns - 1)) selection.merge( proxy_model.mapSelectionFromSource(_selection), QItemSelectionModel.Select, ) # selected rows must be updated when the same dataset with less rows # appear at the input - it is not handled by selectionChanged # in cases when all selected rows missing in new table self.selected_rows = new_sel elif self.sel_method == SelectionMethods.N_BEST: n_sel = min(self.n_selected, n_rows) selection = QItemSelection( proxy_model.index(0, 0), proxy_model.index(n_sel - 1, n_columns - 1)) else: raise NotImplementedError self.view.selectionModel().select(selection, QItemSelectionModel.ClearAndSelect)
class OWPCA(widget.OWWidget): name = "PCA" description = "Principal component analysis with a scree-diagram." icon = "icons/PCA.svg" priority = 3050 keywords = ["principal component analysis", "linear transformation"] class Inputs: data = Input("Data", Table) class Outputs: transformed_data = Output("Transformed data", Table) components = Output("Components", Table) pca = Output("PCA", PCA, dynamic=False) preprocessor = Output("Preprocessor", Preprocess) settingsHandler = settings.DomainContextHandler() ncomponents = settings.Setting(2) variance_covered = settings.Setting(100) batch_size = settings.Setting(100) address = settings.Setting('') auto_update = settings.Setting(True) auto_commit = settings.Setting(True) normalize = settings.ContextSetting(True) decomposition_idx = settings.ContextSetting(0) maxp = settings.Setting(20) axis_labels = settings.Setting(10) graph_name = "plot.plotItem" class Warning(widget.OWWidget.Warning): trivial_components = widget.Msg( "All components of the PCA are trivial (explain 0 variance). " "Input data is constant (or near constant).") class Error(widget.OWWidget.Error): no_features = widget.Msg("At least 1 feature is required") no_instances = widget.Msg("At least 1 data instance is required") sparse_data = widget.Msg("Sparse data is not supported") def __init__(self): super().__init__() self.data = None self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = False self._init_projector() # Components Selection box = gui.vBox(self.controlArea, "Components Selection") form = QFormLayout() box.layout().addLayout(form) self.components_spin = gui.spin( box, self, "ncomponents", 1, MAX_COMPONENTS, callback=self._update_selection_component_spin, keyboardTracking=False ) self.components_spin.setSpecialValueText("All") self.variance_spin = gui.spin( box, self, "variance_covered", 1, 100, callback=self._update_selection_variance_spin, keyboardTracking=False ) self.variance_spin.setSuffix("%") form.addRow("Components:", self.components_spin) form.addRow("Variance covered:", self.variance_spin) # Incremental learning self.sampling_box = gui.vBox(self.controlArea, "Incremental learning") self.addresstext = QLineEdit(box) self.addresstext.setPlaceholderText('Remote server') if self.address: self.addresstext.setText(self.address) self.sampling_box.layout().addWidget(self.addresstext) form = QFormLayout() self.sampling_box.layout().addLayout(form) self.batch_spin = gui.spin( self.sampling_box, self, "batch_size", 50, 100000, step=50, keyboardTracking=False) form.addRow("Batch size ~ ", self.batch_spin) self.start_button = gui.button( self.sampling_box, self, "Start remote computation", callback=self.start, autoDefault=False, tooltip="Start/abort computation on the server") self.start_button.setEnabled(False) gui.checkBox(self.sampling_box, self, "auto_update", "Periodically fetch model", callback=self.update_model) self.__timer = QTimer(self, interval=2000) self.__timer.timeout.connect(self.get_model) self.sampling_box.setVisible(remotely) # Decomposition self.decomposition_box = gui.radioButtons( self.controlArea, self, "decomposition_idx", [d.name for d in DECOMPOSITIONS], box="Decomposition", callback=self._update_decomposition ) # Options self.options_box = gui.vBox(self.controlArea, "Options") self.normalize_box = gui.checkBox( self.options_box, self, "normalize", "Normalize data", callback=self._update_normalize ) self.maxp_spin = gui.spin( self.options_box, self, "maxp", 1, MAX_COMPONENTS, label="Show only first", callback=self._setup_plot, keyboardTracking=False ) self.controlArea.layout().addStretch() gui.auto_commit(self.controlArea, self, "auto_commit", "Apply", checkbox_label="Apply automatically") self.plot = pg.PlotWidget(background="w") axis = self.plot.getAxis("bottom") axis.setLabel("Principal Components") axis = self.plot.getAxis("left") axis.setLabel("Proportion of variance") self.plot_horlabels = [] self.plot_horlines = [] self.plot.getViewBox().setMenuEnabled(False) self.plot.getViewBox().setMouseEnabled(False, False) self.plot.showGrid(True, True, alpha=0.5) self.plot.setRange(xRange=(0.0, 1.0), yRange=(0.0, 1.0)) self.mainArea.layout().addWidget(self.plot) self._update_normalize() def update_model(self): self.get_model() if self.auto_update and self.rpca and not self.rpca.ready(): self.__timer.start(2000) else: self.__timer.stop() def update_buttons(self, sparse_data=False): if sparse_data: self.normalize = False buttons = self.decomposition_box.buttons for cls, button in zip(DECOMPOSITIONS, buttons): button.setDisabled(sparse_data and not cls.supports_sparse) if not buttons[self.decomposition_idx].isEnabled(): # Set decomposition index to first sparse-enabled decomposition for i, cls in enumerate(DECOMPOSITIONS): if cls.supports_sparse: self.decomposition_idx = i break self._init_projector() def start(self): if 'Abort' in self.start_button.text(): self.rpca.abort() self.__timer.stop() self.start_button.setText("Start remote computation") else: self.address = self.addresstext.text() with remote.server(self.address): from Orange.projection.pca import RemotePCA maxiter = (1e5 + self.data.approx_len()) / self.batch_size * 3 self.rpca = RemotePCA(self.data, self.batch_size, int(maxiter)) self.update_model() self.start_button.setText("Abort remote computation") @Inputs.data def set_data(self, data): self.closeContext() self.clear_messages() self.clear() self.start_button.setEnabled(False) self.information() self.data = None if isinstance(data, SqlTable): if data.approx_len() < AUTO_DL_LIMIT: data = Table(data) elif not remotely: self.information("Data has been sampled") data_sample = data.sample_time(1, no_cache=True) data_sample.download_data(2000, partial=True) data = Table(data_sample) else: # data was big and remote available self.sampling_box.setVisible(True) self.start_button.setText("Start remote computation") self.start_button.setEnabled(True) if not isinstance(data, SqlTable): self.sampling_box.setVisible(False) if isinstance(data, Table): if len(data.domain.attributes) == 0: self.Error.no_features() self.clear_outputs() return if len(data) == 0: self.Error.no_instances() self.clear_outputs() return self.openContext(data) sparse_data = data is not None and data.is_sparse() self.normalize_box.setDisabled(sparse_data) self.update_buttons(sparse_data=sparse_data) self.data = data self.fit() def fit(self): self.clear() self.Warning.trivial_components.clear() if self.data is None: return data = self.data self._pca_projector.preprocessors = \ self._pca_preprocessors + ([Normalize()] if self.normalize else []) if not isinstance(data, SqlTable): pca = self._pca_projector(data) variance_ratio = pca.explained_variance_ratio_ cumulative = numpy.cumsum(variance_ratio) if numpy.isfinite(cumulative[-1]): self.components_spin.setRange(0, len(cumulative)) self._pca = pca self._variance_ratio = variance_ratio self._cumulative = cumulative self._setup_plot() else: self.Warning.trivial_components() self.unconditional_commit() def clear(self): self._pca = None self._transformed = None self._variance_ratio = None self._cumulative = None self._line = None self.plot_horlabels = [] self.plot_horlines = [] self.plot.clear() def clear_outputs(self): self.Outputs.transformed_data.send(None) self.Outputs.components.send(None) self.Outputs.pca.send(self._pca_projector) self.Outputs.preprocessor.send(None) def get_model(self): if self.rpca is None: return if self.rpca.ready(): self.__timer.stop() self.start_button.setText("Restart (finished)") self._pca = self.rpca.get_state() if self._pca is None: return self._variance_ratio = self._pca.explained_variance_ratio_ self._cumulative = numpy.cumsum(self._variance_ratio) self._setup_plot() self._transformed = None self.commit() def _setup_plot(self): self.plot.clear() if self._pca is None: return explained_ratio = self._variance_ratio explained = self._cumulative p = min(len(self._variance_ratio), self.maxp) self.plot.plot(numpy.arange(p), explained_ratio[:p], pen=pg.mkPen(QColor(Qt.red), width=2), antialias=True, name="Variance") self.plot.plot(numpy.arange(p), explained[:p], pen=pg.mkPen(QColor(Qt.darkYellow), width=2), antialias=True, name="Cumulative Variance") cutpos = self._nselected_components() - 1 self._line = pg.InfiniteLine( angle=90, pos=cutpos, movable=True, bounds=(0, p - 1)) self._line.setCursor(Qt.SizeHorCursor) self._line.setPen(pg.mkPen(QColor(Qt.black), width=2)) self._line.sigPositionChanged.connect(self._on_cut_changed) self.plot.addItem(self._line) self.plot_horlines = ( pg.PlotCurveItem(pen=pg.mkPen(QColor(Qt.blue), style=Qt.DashLine)), pg.PlotCurveItem(pen=pg.mkPen(QColor(Qt.blue), style=Qt.DashLine))) self.plot_horlabels = ( pg.TextItem(color=QColor(Qt.black), anchor=(1, 0)), pg.TextItem(color=QColor(Qt.black), anchor=(1, 1))) for item in self.plot_horlabels + self.plot_horlines: self.plot.addItem(item) self._set_horline_pos() self.plot.setRange(xRange=(0.0, p - 1), yRange=(0.0, 1.0)) self._update_axis() def _set_horline_pos(self): cutidx = self.ncomponents - 1 for line, label, curve in zip(self.plot_horlines, self.plot_horlabels, (self._variance_ratio, self._cumulative)): y = curve[cutidx] line.setData([-1, cutidx], 2 * [y]) label.setPos(cutidx, y) label.setPlainText("{:.3f}".format(y)) def _on_cut_changed(self, line): # cut changed by means of a cut line over the scree plot. value = int(round(line.value())) self._line.setValue(value) current = self._nselected_components() components = value + 1 if not (self.ncomponents == 0 and components == len(self._variance_ratio)): self.ncomponents = components self._set_horline_pos() if self._pca is not None: var = self._cumulative[components - 1] if numpy.isfinite(var): self.variance_covered = int(var * 100) if current != self._nselected_components(): self._invalidate_selection() def _update_selection_component_spin(self): # cut changed by "ncomponents" spin. if self._pca is None: self._invalidate_selection() return if self.ncomponents == 0: # Special "All" value cut = len(self._variance_ratio) else: cut = self.ncomponents var = self._cumulative[cut - 1] if numpy.isfinite(var): self.variance_covered = int(var * 100) if numpy.floor(self._line.value()) + 1 != cut: self._line.setValue(cut - 1) self._invalidate_selection() def _update_selection_variance_spin(self): # cut changed by "max variance" spin. if self._pca is None: return cut = numpy.searchsorted(self._cumulative, self.variance_covered / 100.0) + 1 cut = min(cut, len(self._cumulative)) self.ncomponents = cut if numpy.floor(self._line.value()) + 1 != cut: self._line.setValue(cut - 1) self._invalidate_selection() def _update_normalize(self): self.fit() if self.data is None: self._invalidate_selection() def _init_projector(self): cls = DECOMPOSITIONS[self.decomposition_idx] self._pca_projector = cls(n_components=MAX_COMPONENTS) self._pca_projector.component = self.ncomponents self._pca_preprocessors = cls.preprocessors def _update_decomposition(self): self._init_projector() self._update_normalize() def _nselected_components(self): """Return the number of selected components.""" if self._pca is None: return 0 if self.ncomponents == 0: # Special "All" value max_comp = len(self._variance_ratio) else: max_comp = self.ncomponents var_max = self._cumulative[max_comp - 1] if var_max != numpy.floor(self.variance_covered / 100.0): cut = max_comp assert numpy.isfinite(var_max) self.variance_covered = int(var_max * 100) else: self.ncomponents = cut = numpy.searchsorted( self._cumulative, self.variance_covered / 100.0) + 1 return cut def _invalidate_selection(self): self.commit() def _update_axis(self): p = min(len(self._variance_ratio), self.maxp) axis = self.plot.getAxis("bottom") d = max((p-1)//(self.axis_labels-1), 1) axis.setTicks([[(i, str(i+1)) for i in range(0, p, d)]]) def commit(self): transformed = components = pp = None if self._pca is not None: if self._transformed is None: # Compute the full transform (MAX_COMPONENTS components) only once. self._transformed = self._pca(self.data) transformed = self._transformed domain = Domain( transformed.domain.attributes[:self.ncomponents], self.data.domain.class_vars, self.data.domain.metas ) transformed = transformed.from_table(domain, transformed) # prevent caching new features by defining compute_value dom = Domain([ContinuousVariable(a.name, compute_value=lambda _: None) for a in self._pca.orig_domain.attributes], metas=[StringVariable(name='component')]) metas = numpy.array([['PC{}'.format(i + 1) for i in range(self.ncomponents)]], dtype=object).T components = Table(dom, self._pca.components_[:self.ncomponents], metas=metas) components.name = 'components' pp = ApplyDomain(domain, "PCA") self._pca_projector.component = self.ncomponents self.Outputs.transformed_data.send(transformed) self.Outputs.components.send(components) self.Outputs.pca.send(self._pca_projector) self.Outputs.preprocessor.send(pp) def send_report(self): if self.data is None: return self.report_items(( ("Decomposition", DECOMPOSITIONS[self.decomposition_idx].name), ("Normalize data", str(self.normalize)), ("Selected components", self.ncomponents), ("Explained variance", "{:.3f} %".format(self.variance_covered)) )) self.report_plot() @classmethod def migrate_settings(cls, settings, version): if "variance_covered" in settings: # Due to the error in gh-1896 the variance_covered was persisted # as a NaN value, causing a TypeError in the widgets `__init__`. vc = settings["variance_covered"] if isinstance(vc, numbers.Real): if numpy.isfinite(vc): vc = int(vc) else: vc = 100 settings["variance_covered"] = vc if settings.get("ncomponents", 0) > MAX_COMPONENTS: settings["ncomponents"] = MAX_COMPONENTS
class VariableEditor(QWidget): """ An editor widget for a variable. Can edit the variable name, and its attributes dictionary. """ variable_changed = Signal() def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.var = None # type: Optional[Variable] layout = QVBoxLayout() self.setLayout(layout) self.form = form = QFormLayout( fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, objectName="editor-form-layout") layout.addLayout(self.form) self.name_edit = QLineEdit(objectName="name-editor") self.name_edit.editingFinished.connect( lambda: self.name_edit.isModified() and self.on_name_changed()) form.addRow("Name:", self.name_edit) vlayout = QVBoxLayout(margin=0, spacing=1) self.labels_edit = view = QTreeView( objectName="annotation-pairs-edit", rootIsDecorated=False, editTriggers=QTreeView.DoubleClicked | QTreeView.EditKeyPressed, ) self.labels_model = model = DictItemsModel() view.setModel(model) view.selectionModel().selectionChanged.connect( self.on_label_selection_changed) agrp = QActionGroup(view, objectName="annotate-action-group") action_add = QAction("+", self, objectName="action-add-label", toolTip="Add a new label.", shortcut=QKeySequence(QKeySequence.New), shortcutContext=Qt.WidgetShortcut) action_delete = QAction("\N{MINUS SIGN}", self, objectName="action-delete-label", toolTip="Remove selected label.", shortcut=QKeySequence(QKeySequence.Delete), shortcutContext=Qt.WidgetShortcut) agrp.addAction(action_add) agrp.addAction(action_delete) view.addActions([action_add, action_delete]) def add_label(): row = [QStandardItem(), QStandardItem()] model.appendRow(row) idx = model.index(model.rowCount() - 1, 0) view.setCurrentIndex(idx) view.edit(idx) def remove_label(): rows = view.selectionModel().selectedRows(0) if rows: assert len(rows) == 1 idx = rows[0].row() model.removeRow(idx) action_add.triggered.connect(add_label) action_delete.triggered.connect(remove_label) agrp.setEnabled(False) self.add_label_action = action_add self.remove_label_action = action_delete # Necessary signals to know when the labels change model.dataChanged.connect(self.on_labels_changed) model.rowsInserted.connect(self.on_labels_changed) model.rowsRemoved.connect(self.on_labels_changed) vlayout.addWidget(self.labels_edit) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) button = FixedSizeButton( self, defaultAction=self.add_label_action, accessibleName="Add", ) hlayout.addWidget(button) button = FixedSizeButton( self, defaultAction=self.remove_label_action, accessibleName="Remove", ) hlayout.addWidget(button) hlayout.addStretch(10) vlayout.addLayout(hlayout) form.addRow("Labels:", vlayout) def set_data(self, var, transform=()): # type: (Optional[Variable], Sequence[Transform]) -> None """ Set the variable to edit. """ self.clear() self.var = var if var is not None: name = var.name annotations = var.annotations for tr in transform: if isinstance(tr, Rename): name = tr.name elif isinstance(tr, Annotate): annotations = tr.annotations self.name_edit.setText(name) self.labels_model.set_dict(dict(annotations)) self.add_label_action.actionGroup().setEnabled(True) else: self.add_label_action.actionGroup().setEnabled(False) def get_data(self): """Retrieve the modified variable. """ if self.var is None: return None, [] name = self.name_edit.text().strip() labels = tuple(sorted(self.labels_model.get_dict().items())) tr = [] if self.var.name != name: tr.append(Rename(name)) if self.var.annotations != labels: tr.append(Annotate(labels)) return self.var, tr def clear(self): """Clear the editor state. """ self.var = None self.name_edit.setText("") self.labels_model.setRowCount(0) @Slot() def on_name_changed(self): self.variable_changed.emit() @Slot() def on_labels_changed(self): self.variable_changed.emit() @Slot() def on_label_selection_changed(self): selected = self.labels_edit.selectionModel().selectedRows() self.remove_label_action.setEnabled(bool(len(selected)))
class OWDatabasesUpdate(OWWidget): name = "Databases Update" description = "Update local systems biology databases." icon = "../widgets/icons/Databases.svg" priority = 10 inputs = [] outputs = [] want_main_area = False def __init__(self, parent=None, signalManager=None, name="Databases update"): OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False) self.searchString = "" fbox = gui.widgetBox(self.controlArea, "Filter") self.completer = TokenListCompleter( self, caseSensitivity=Qt.CaseInsensitive) self.lineEditFilter = QLineEdit(textChanged=self.SearchUpdate) self.lineEditFilter.setCompleter(self.completer) fbox.layout().addWidget(self.lineEditFilter) box = gui.widgetBox(self.controlArea, "Files") self.filesView = QTreeWidget(self) self.filesView.setHeaderLabels( ["", "Data Source", "Update", "Last Updated", "Size"]) self.filesView.setRootIsDecorated(False) self.filesView.setUniformRowHeights(True) self.filesView.setSelectionMode(QAbstractItemView.NoSelection) self.filesView.setSortingEnabled(True) self.filesView.sortItems(1, Qt.AscendingOrder) self.filesView.setItemDelegateForColumn( 0, UpdateOptionsItemDelegate(self.filesView)) self.filesView.model().layoutChanged.connect(self.SearchUpdate) box.layout().addWidget(self.filesView) box = gui.widgetBox(self.controlArea, orientation="horizontal") self.updateButton = gui.button( box, self, "Update all", callback=self.UpdateAll, tooltip="Update all updatable files", ) self.downloadButton = gui.button( box, self, "Download all", callback=self.DownloadFiltered, tooltip="Download all filtered files shown" ) self.cancelButton = gui.button( box, self, "Cancel", callback=self.Cancel, tooltip="Cancel scheduled downloads/updates." ) self.retryButton = gui.button( box, self, "Reconnect", callback=self.RetrieveFilesList ) self.retryButton.hide() gui.rubber(box) self.warning(0) box = gui.widgetBox(self.controlArea, orientation="horizontal") gui.rubber(box) self.infoLabel = QLabel() self.infoLabel.setAlignment(Qt.AlignCenter) self.controlArea.layout().addWidget(self.infoLabel) self.infoLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.updateItems = [] self.resize(800, 600) self.progress = ProgressState(self, maximum=3) self.progress.valueChanged.connect(self._updateProgress) self.progress.rangeChanged.connect(self._updateProgress) self.executor = ThreadExecutor( threadPool=QThreadPool(maxThreadCount=2) ) task = Task(self, function=self.RetrieveFilesList) task.exceptionReady.connect(self.HandleError) task.start() self._tasks = [] self._haveProgress = False def RetrieveFilesList(self): self.retryButton.hide() self.warning(0) self.progress.setRange(0, 3) task = Task(function=partial(retrieveFilesList, methodinvoke(self.progress, "advance"))) task.resultReady.connect(self.SetFilesList) task.exceptionReady.connect(self.HandleError) self.executor.submit(task) self.setEnabled(False) def SetFilesList(self, serverInfo): """ Set the files to show. """ self.setEnabled(True) localInfo = serverfiles.allinfo() all_tags = set() self.filesView.clear() self.updateItems = [] for item in join_info_dict(localInfo, serverInfo): tree_item = UpdateTreeWidgetItem(item) options_widget = UpdateOptionsWidget(item.state) options_widget.item = item options_widget.installClicked.connect( partial(self.SubmitDownloadTask, item.domain, item.filename) ) options_widget.removeClicked.connect( partial(self.SubmitRemoveTask, item.domain, item.filename) ) self.updateItems.append((item, tree_item, options_widget)) all_tags.update(item.tags) self.filesView.addTopLevelItems( [tree_item for _, tree_item, _ in self.updateItems] ) for item, tree_item, options_widget in self.updateItems: self.filesView.setItemWidget(tree_item, 0, options_widget) # Add an update button if the file is updateable if item.state == OUTDATED: button = QToolButton( None, text="Update", maximumWidth=120, minimumHeight=20, maximumHeight=20 ) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) button.clicked.connect( partial(self.SubmitDownloadTask, item.domain, item.filename) ) self.filesView.setItemWidget(tree_item, 2, button) self.progress.advance() self.filesView.setColumnWidth(0, self.filesView.sizeHintForColumn(0)) for column in range(1, 4): contents_hint = self.filesView.sizeHintForColumn(column) header_hint = self.filesView.header().sectionSizeHint(column) width = max(min(contents_hint, 400), header_hint) self.filesView.setColumnWidth(column, width) hints = [hint for hint in sorted(all_tags) if not hint.startswith("#")] self.completer.setTokenList(hints) self.SearchUpdate() self.UpdateInfoLabel() self.toggleButtons() self.cancelButton.setEnabled(False) self.progress.setRange(0, 0) def buttonCheck(self, selected_items, state, button): for item in selected_items: if item.state != state: button.setEnabled(False) else: button.setEnabled(True) break def toggleButtons(self): selected_items = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()] self.buttonCheck(selected_items, OUTDATED, self.updateButton) self.buttonCheck(selected_items, AVAILABLE, self.downloadButton) def HandleError(self, exception): if isinstance(exception, ConnectionError): self.warning(0, "Could not connect to server! Check your connection " "and try to reconnect.") self.SetFilesList({}) self.retryButton.show() else: sys.excepthook(type(exception), exception, None) self.progress.setRange(0, 0) self.setEnabled(True) def UpdateInfoLabel(self): local = [item for item, tree_item, _ in self.updateItems if item.state != AVAILABLE and not tree_item.isHidden()] size = sum(float(item.size) for item in local) onServer = [item for item, tree_item, _ in self.updateItems if not tree_item.isHidden()] sizeOnServer = sum(float(item.size) for item in onServer) text = ("%i items, %s (on server: %i items, %s)" % (len(local), sizeof_fmt(size), len(onServer), sizeof_fmt(sizeOnServer))) self.infoLabel.setText(text) def UpdateAll(self): self.warning(0) for item, tree_item, _ in self.updateItems: if item.state == OUTDATED and not tree_item.isHidden(): self.SubmitDownloadTask(item.domain, item.filename) def DownloadFiltered(self): # TODO: submit items in the order shown. for item, tree_item, _ in self.updateItems: if not tree_item.isHidden() and item.state in \ [AVAILABLE, OUTDATED]: self.SubmitDownloadTask(item.domain, item.filename) def SearchUpdate(self, searchString=None): strings = str(self.lineEditFilter.text()).split() for item, tree_item, _ in self.updateItems: hide = not all(UpdateItem_match(item, string) for string in strings) tree_item.setHidden(hide) self.UpdateInfoLabel() self.toggleButtons() def SubmitDownloadTask(self, domain, filename): """ Submit the (domain, filename) to be downloaded/updated. """ self.cancelButton.setEnabled(True) index = self.updateItemIndex(domain, filename) _, tree_item, opt_widget = self.updateItems[index] sf = LocalFiles(serverfiles.PATH, serverfiles.ServerFiles()) task = DownloadTask(domain, filename, sf) self.progress.adjustRange(0, 100) pb = ItemProgressBar(self.filesView) pb.setRange(0, 100) pb.setTextVisible(False) task.advanced.connect(pb.advance) task.advanced.connect(self.progress.advance) task.finished.connect(pb.hide) task.finished.connect(self.onDownloadFinished, Qt.QueuedConnection) task.exception.connect(self.onDownloadError, Qt.QueuedConnection) self.filesView.setItemWidget(tree_item, 2, pb) # Clear the text so it does not show behind the progress bar. tree_item.setData(2, Qt.DisplayRole, "") pb.show() # Disable the options widget opt_widget.setEnabled(False) self._tasks.append(task) self.executor.submit(task) def EndDownloadTask(self, task): future = task.future() index = self.updateItemIndex(task.domain, task.filename) item, tree_item, opt_widget = self.updateItems[index] self.filesView.removeItemWidget(tree_item, 2) opt_widget.setEnabled(True) if future.cancelled(): # Restore the previous state tree_item.setUpdateItem(item) opt_widget.setState(item.state) elif future.exception(): tree_item.setUpdateItem(item) opt_widget.setState(item.state) # Show the exception string in the size column. self.warning(0, "Error while downloading. Check your connection " "and retry.") # recreate button for download button = QToolButton( None, text="Retry", maximumWidth=120, minimumHeight=20, maximumHeight=20 ) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) button.clicked.connect( partial(self.SubmitDownloadTask, item.domain, item.filename) ) self.filesView.setItemWidget(tree_item, 2, button) else: # get the new updated info dict and replace the the old item self.warning(0) info = serverfiles.info(item.domain, item.filename) new_item = update_item_from_info(item.domain, item.filename, info, info) self.updateItems[index] = (new_item, tree_item, opt_widget) tree_item.setUpdateItem(new_item) opt_widget.setState(new_item.state) self.UpdateInfoLabel() def SubmitRemoveTask(self, domain, filename): serverfiles.LOCALFILES.remove(domain, filename) index = self.updateItemIndex(domain, filename) item, tree_item, opt_widget = self.updateItems[index] if item.info_server: new_item = item._replace(state=AVAILABLE, local=None, info_local=None) else: new_item = item._replace(local=None, info_local=None) # Disable the options widget. No more actions can be performed # for the item. opt_widget.setEnabled(False) tree_item.setUpdateItem(new_item) opt_widget.setState(new_item.state) self.updateItems[index] = (new_item, tree_item, opt_widget) self.UpdateInfoLabel() def Cancel(self): """ Cancel all pending update/download tasks (that have not yet started). """ for task in self._tasks: task.future().cancel() def onDeleteWidget(self): self.Cancel() self.executor.shutdown(wait=False) OWWidget.onDeleteWidget(self) def onDownloadFinished(self): # on download completed/canceled/error assert QThread.currentThread() is self.thread() for task in list(self._tasks): future = task.future() if future.done(): self.EndDownloadTask(task) self._tasks.remove(task) if not self._tasks: # Clear/reset the overall progress self.progress.setRange(0, 0) self.cancelButton.setEnabled(False) def onDownloadError(self, exc_info): sys.excepthook(*exc_info) self.warning(0, "Error while downloading. Check your connection and " "retry.") def updateItemIndex(self, domain, filename): for i, (item, _, _) in enumerate(self.updateItems): if item.domain == domain and item.filename == filename: return i raise ValueError("%r, %r not in update list" % (domain, filename)) def _updateProgress(self, *args): rmin, rmax = self.progress.range() if rmin != rmax: if not self._haveProgress: self._haveProgress = True self.progressBarInit() self.progressBarSet(self.progress.ratioCompleted() * 100, processEvents=None) if rmin == rmax: self._haveProgress = False self.progressBarFinished()
def __init__(self): super().__init__() self.local_cache_path = os.path.join(data_dir(), self.DATASET_DIR) self._header_labels = [header['label'] for _, header in self.HEADER_SCHEMA] self._header_index = namedtuple('_header_index', [info_tag for info_tag, _ in self.HEADER_SCHEMA]) self.Header = self._header_index(*[index for index, _ in enumerate(self._header_labels)]) self.__awaiting_state = None # type: Optional[_FetchState] box = gui.widgetBox(self.controlArea, "Info") self.infolabel = QLabel(text="Initializing...\n\n") box.layout().addWidget(self.infolabel) gui.widgetLabel(self.mainArea, "Filter") self.filterLineEdit = QLineEdit( textChanged=self.filter ) self.mainArea.layout().addWidget(self.filterLineEdit) self.splitter = QSplitter(orientation=Qt.Vertical) self.view = QTreeView( sortingEnabled=True, selectionMode=QTreeView.SingleSelection, alternatingRowColors=True, rootIsDecorated=False, editTriggers=QTreeView.NoEditTriggers, uniformRowHeights=True, ) box = gui.widgetBox(self.splitter, "Description", addToLayout=False) self.descriptionlabel = QLabel( wordWrap=True, textFormat=Qt.RichText, ) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) ) self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) self.splitter.addWidget(self.view) self.splitter.addWidget(box) self.splitter.setSizes([300, 200]) self.splitter.splitterMoved.connect( lambda: setattr(self, "splitter_state", bytes(self.splitter.saveState())) ) self.mainArea.layout().addWidget(self.splitter) self.controlArea.layout().addStretch(10) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Data") proxy = QSortFilterProxyModel() proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) if self.splitter_state: self.splitter.restoreState(self.splitter_state) self.assign_delegates() self.setBlocking(True) self.setStatusMessage("Initializing") self._executor = ThreadPoolExecutor(max_workers=1) f = self._executor.submit(self.list_remote) w = FutureWatcher(f, parent=self) w.done.connect(self.__set_index)
def __init__(self): super().__init__() self.local_cache_path = os.path.join(data_dir(), self.DATASET_DIR) self.__awaiting_state = None # type: Optional[_FetchState] box = gui.widgetBox(self.controlArea, "Info") self.infolabel = QLabel(text="Initializing...\n\n") box.layout().addWidget(self.infolabel) gui.widgetLabel(self.mainArea, "Filter") self.filterLineEdit = QLineEdit( textChanged=self.filter ) self.mainArea.layout().addWidget(self.filterLineEdit) self.splitter = QSplitter(orientation=Qt.Vertical) self.view = QTreeView( sortingEnabled=True, selectionMode=QTreeView.SingleSelection, alternatingRowColors=True, rootIsDecorated=False, editTriggers=QTreeView.NoEditTriggers, ) box = gui.widgetBox(self.splitter, "Description", addToLayout=False) self.descriptionlabel = QLabel( wordWrap=True, textFormat=Qt.RichText, ) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse) ) self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) self.splitter.addWidget(self.view) self.splitter.addWidget(box) self.splitter.setSizes([300, 200]) self.splitter.splitterMoved.connect( lambda: setattr(self, "splitter_state", bytes(self.splitter.saveState())) ) self.mainArea.layout().addWidget(self.splitter) self.controlArea.layout().addStretch(10) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Data") model = QStandardItemModel(self) model.setHorizontalHeaderLabels(HEADER) proxy = QSortFilterProxyModel() proxy.setSourceModel(model) proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) if self.splitter_state: self.splitter.restoreState(self.splitter_state) self.view.setItemDelegateForColumn( Header.Size, SizeDelegate(self)) self.view.setItemDelegateForColumn( Header.Local, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.view.setItemDelegateForColumn( Header.Instances, NumericalDelegate(self)) self.view.setItemDelegateForColumn( Header.Variables, NumericalDelegate(self)) self.view.resizeColumnToContents(Header.Local) if self.header_state: self.view.header().restoreState(self.header_state) self.setBlocking(True) self.setStatusMessage("Initializing") self._executor = ThreadPoolExecutor(max_workers=1) f = self._executor.submit(self.list_remote) w = FutureWatcher(f, parent=self) w.done.connect(self.__set_index)
class VizRankDialog(QDialog, ProgressBarMixin, WidgetMessagesMixin): """ Base class for VizRank dialogs, providing a GUI with a table and a button, and the skeleton for managing the evaluation of visualizations. Derived classes must provide methods - `iterate_states` for generating combinations (e.g. pairs of attritutes), - `compute_score(state)` for computing the score of a combination, - `row_for_state(state)` that returns a list of items inserted into the table for the given state. and, optionally, - `state_count` that returns the number of combinations (used for progress bar) - `on_selection_changed` that handles event triggered when the user selects a table row. The method should emit signal `VizRankDialog.selectionChanged(object)`. - `bar_length` returns the length of the bar corresponding to the score. The class provides a table and a button. A widget constructs a single instance of this dialog in its `__init__`, like (in Sieve) by using a convenience method :obj:`add_vizrank`:: self.vizrank, self.vizrank_button = SieveRank.add_vizrank( box, self, "Score Combinations", self.set_attr) When the widget receives new data, it must call the VizRankDialog's method :obj:`VizRankDialog.initialize()` to clear the GUI and reset the state. Clicking the Start button calls method `run` (and renames the button to Pause). Run sets up a progress bar by getting the number of combinations from :obj:`VizRankDialog.state_count()`. It restores the paused state (if any) and calls generator :obj:`VizRankDialog.iterate_states()`. For each generated state, it calls :obj:`VizRankDialog.score(state)`, which must return the score (lower is better) for this state. If the returned state is not `None`, the data returned by `row_for_state` is inserted at the appropriate place in the table. Args: master (Orange.widget.OWWidget): widget to which the dialog belongs Attributes: master (Orange.widget.OWWidget): widget to which the dialog belongs captionTitle (str): the caption for the dialog. This can be a class attribute. `captionTitle` is used by the `ProgressBarMixin`. """ captionTitle = "" processingStateChanged = Signal(int) progressBarValueChanged = Signal(float) messageActivated = Signal(Msg) messageDeactivated = Signal(Msg) selectionChanged = Signal(object) class Information(WidgetMessagesMixin.Information): nothing_to_rank = Msg("There is nothing to rank.") def __init__(self, master): """Initialize the attributes and set up the interface""" QDialog.__init__(self, master, windowTitle=self.captionTitle) WidgetMessagesMixin.__init__(self) self.setLayout(QVBoxLayout()) self.insert_message_bar() self.layout().insertWidget(0, self.message_bar) self.master = master self.keep_running = False self.scheduled_call = None self.saved_state = None self.saved_progress = 0 self.scores = [] self.add_to_model = queue.Queue() self.update_timer = QTimer(self) self.update_timer.timeout.connect(self._update) self.update_timer.setInterval(200) self._thread = None self._worker = None self.filter = QLineEdit() self.filter.setPlaceholderText("Filter ...") self.filter.textChanged.connect(self.filter_changed) self.layout().addWidget(self.filter) # Remove focus from line edit self.setFocus(Qt.ActiveWindowFocusReason) self.rank_model = QStandardItemModel(self) self.model_proxy = QSortFilterProxyModel( self, filterCaseSensitivity=False) self.model_proxy.setSourceModel(self.rank_model) self.rank_table = view = QTableView( selectionBehavior=QTableView.SelectRows, selectionMode=QTableView.SingleSelection, showGrid=False, editTriggers=gui.TableView.NoEditTriggers) if self._has_bars: view.setItemDelegate(TableBarItem()) else: view.setItemDelegate(HorizontalGridDelegate()) view.setModel(self.model_proxy) view.selectionModel().selectionChanged.connect( self.on_selection_changed) view.horizontalHeader().setStretchLastSection(True) view.horizontalHeader().hide() self.layout().addWidget(view) self.button = gui.button( self, self, "Start", callback=self.toggle, default=True) @property def _has_bars(self): return type(self).bar_length is not VizRankDialog.bar_length @classmethod def add_vizrank(cls, widget, master, button_label, set_attr_callback): """ Equip the widget with VizRank button and dialog, and monkey patch the widget's `closeEvent` and `hideEvent` to close/hide the vizrank, too. Args: widget (QWidget): the widget into whose layout to insert the button master (Orange.widgets.widget.OWWidget): the master widget button_label: the label for the button set_attr_callback: the callback for setting the projection chosen in the vizrank Returns: tuple with Vizrank dialog instance and push button """ # Monkey patching could be avoided by mixing-in the class (not # necessarily a good idea since we can make a mess of multiple # defined/derived closeEvent and hideEvent methods). Furthermore, # per-class patching would be better than per-instance, but we don't # want to mess with meta-classes either. vizrank = cls(master) button = gui.button( widget, master, button_label, callback=vizrank.reshow, enabled=False) vizrank.selectionChanged.connect(lambda args: set_attr_callback(*args)) master_close_event = master.closeEvent master_hide_event = master.hideEvent master_delete_event = master.onDeleteWidget def closeEvent(event): vizrank.close() master_close_event(event) def hideEvent(event): vizrank.hide() master_hide_event(event) def deleteEvent(): vizrank.keep_running = False if vizrank._thread is not None and vizrank._thread.isRunning(): vizrank._thread.quit() vizrank._thread.wait() master_delete_event() master.closeEvent = closeEvent master.hideEvent = hideEvent master.onDeleteWidget = deleteEvent return vizrank, button def reshow(self): """Put the widget on top of all windows """ self.show() self.raise_() self.activateWindow() def initialize(self): """ Clear and initialize the dialog. This method must be called by the widget when the data is reset, e.g. from `set_data` handler. """ if self._thread is not None and self._thread.isRunning(): self.keep_running = False self._thread.quit() self._thread.wait() self.keep_running = False self.scheduled_call = None self.saved_state = None self.saved_progress = 0 self.update_timer.stop() self.progressBarFinished() self.scores = [] self._update_model() # empty queue self.rank_model.clear() self.button.setText("Start") self.button.setEnabled(self.check_preconditions()) self._thread = QThread(self) self._worker = Worker(self) self._worker.moveToThread(self._thread) self._worker.stopped.connect(self._thread.quit) self._worker.stopped.connect(self._select_first_if_none) self._worker.stopped.connect(self._stopped) self._worker.done.connect(self._done) self._thread.started.connect(self._worker.do_work) def filter_changed(self, text): self.model_proxy.setFilterFixedString(text) def stop_and_reset(self, reset_method=None): if self.keep_running: self.scheduled_call = reset_method or self.initialize self.keep_running = False else: self.initialize() def check_preconditions(self): """Check whether there is sufficient data for ranking.""" return True def on_selection_changed(self, selected, deselected): """ Set the new visualization in the widget when the user select a row in the table. If derived class does not reimplement this, the table gives the information but the user can't click it to select the visualization. Args: selected: the index of the selected item deselected: the index of the previously selected item """ pass def iterate_states(self, initial_state): """ Generate all possible states (e.g. attribute combinations) for the given data. The content of the generated states is specific to the visualization. This method must be defined in the derived classes. Args: initial_state: initial state; None if this is the first call """ raise NotImplementedError def state_count(self): """ Return the number of states for the progress bar. Derived classes should implement this to ensure the proper behaviour of the progress bar""" return 0 def compute_score(self, state): """ Abstract method for computing the score for the given state. Smaller scores are better. Args: state: the state, e.g. the combination of attributes as generated by :obj:`state_count`. """ raise NotImplementedError def bar_length(self, score): """Compute the bar length (between 0 and 1) corresponding to the score. Return `None` if the score cannot be normalized. """ return None def row_for_state(self, score, state): """ Abstract method that return the items that are inserted into the table. Args: score: score, computed by :obj:`compute_score` state: the state, e.g. combination of attributes """ raise NotImplementedError def _select_first_if_none(self): if not self.rank_table.selectedIndexes(): self.rank_table.selectRow(0) def _done(self): self.button.setText("Finished") self.button.setEnabled(False) self.keep_running = False self.saved_state = None def _stopped(self): self.update_timer.stop() self.progressBarFinished() self._update_model() self.stopped() if self.scheduled_call: self.scheduled_call() def _update(self): self._update_model() self._update_progress() def _update_progress(self): self.progressBarSet(int(self.saved_progress * 100 / max(1, self.state_count()))) def _update_model(self): try: while True: pos, row_items = self.add_to_model.get_nowait() self.rank_model.insertRow(pos, row_items) except queue.Empty: pass def toggle(self): """Start or pause the computation.""" self.keep_running = not self.keep_running if self.keep_running: self.button.setText("Pause") self.progressBarInit() self.update_timer.start() self.before_running() self._thread.start() else: self.button.setText("Continue") self._thread.quit() # Need to sync state (the worker must read the keep_running # state and stop) for reliable restart. self._thread.wait() def before_running(self): """Code that is run before running vizrank in its own thread""" pass def stopped(self): """Code that is run after stopping the vizrank thread""" pass
class OWDataSets(OWWidget): name = "Datasets" description = "Load a dataset from an online repository" icon = "icons/DataSets.svg" priority = 20 replaces = ["orangecontrib.prototypes.widgets.owdatasets.OWDataSets"] keywords = ["online"] want_control_area = False # The following constants can be overridden in a subclass # to reuse this widget for a different repository # Take care when refactoring! (used in e.g. single-cell) INDEX_URL = "https://datasets.biolab.si/" DATASET_DIR = "datasets" # override HEADER_SCHEMA to define new columns # if schema is changed override methods: self.assign_delegates and # self.create_model HEADER_SCHEMA = [['islocal', { 'label': '' }], ['title', { 'label': 'Title' }], ['size', { 'label': 'Size' }], ['instances', { 'label': 'Instances' }], ['variables', { 'label': 'Variables' }], ['target', { 'label': 'Target' }], ['tags', { 'label': 'Tags' }]] # type: List[str, dict] IndicatorBrushes = (QBrush(Qt.darkGray), QBrush(QColor(0, 192, 0))) class Error(OWWidget.Error): no_remote_datasets = Msg("Could not fetch dataset list") class Warning(OWWidget.Warning): only_local_datasets = Msg("Could not fetch datasets list, only local " "cached datasets are shown") class Outputs: data = Output("Data", Orange.data.Table) #: Selected dataset id selected_id = settings.Setting(None) # type: Optional[str] #: main area splitter state splitter_state = settings.Setting(b'') # type: bytes header_state = settings.Setting(b'') # type: bytes def __init__(self): super().__init__() self.allinfo_local = {} self.allinfo_remote = {} self.local_cache_path = os.path.join(data_dir(), self.DATASET_DIR) # current_output does not equal selected_id when, for instance, the # data is still downloading self.current_output = None self._header_labels = [ header['label'] for _, header in self.HEADER_SCHEMA ] self._header_index = namedtuple( '_header_index', [info_tag for info_tag, _ in self.HEADER_SCHEMA]) self.Header = self._header_index( *[index for index, _ in enumerate(self._header_labels)]) self.__awaiting_state = None # type: Optional[_FetchState] self.filterLineEdit = QLineEdit( textChanged=self.filter, placeholderText="Search for data set ...") self.mainArea.layout().addWidget(self.filterLineEdit) self.splitter = QSplitter(orientation=Qt.Vertical) self.view = TreeViewWithReturn( sortingEnabled=True, selectionMode=QTreeView.SingleSelection, alternatingRowColors=True, rootIsDecorated=False, editTriggers=QTreeView.NoEditTriggers, uniformRowHeights=True, toolTip="Press Return or double-click to send") # the method doesn't exists yet, pylint: disable=unnecessary-lambda self.view.doubleClicked.connect(self.commit) self.view.returnPressed.connect(self.commit) box = gui.widgetBox(self.splitter, "Description", addToLayout=False) self.descriptionlabel = QLabel( wordWrap=True, textFormat=Qt.RichText, ) self.descriptionlabel = QTextBrowser( openExternalLinks=True, textInteractionFlags=(Qt.TextSelectableByMouse | Qt.LinksAccessibleByMouse)) self.descriptionlabel.setFrameStyle(QTextBrowser.NoFrame) # no (white) text background self.descriptionlabel.viewport().setAutoFillBackground(False) box.layout().addWidget(self.descriptionlabel) self.splitter.addWidget(self.view) self.splitter.addWidget(box) self.splitter.setSizes([300, 200]) self.splitter.splitterMoved.connect(lambda: setattr( self, "splitter_state", bytes(self.splitter.saveState()))) self.mainArea.layout().addWidget(self.splitter) proxy = QSortFilterProxyModel() proxy.setFilterKeyColumn(-1) proxy.setFilterCaseSensitivity(False) self.view.setModel(proxy) if self.splitter_state: self.splitter.restoreState(self.splitter_state) self.assign_delegates() self.setBlocking(True) self.setStatusMessage("Initializing") self._executor = ThreadPoolExecutor(max_workers=1) f = self._executor.submit(self.list_remote) w = FutureWatcher(f, parent=self) w.done.connect(self.__set_index) def assign_delegates(self): # NOTE: All columns must have size hinting delegates. # QTreeView queries only the columns displayed in the viewport so # the layout would be different depending in the horizontal scroll # position self.view.setItemDelegate(UniformHeightDelegate(self)) self.view.setItemDelegateForColumn( self.Header.islocal, UniformHeightIndicatorDelegate(self, role=Qt.DisplayRole, indicatorSize=4)) self.view.setItemDelegateForColumn(self.Header.size, SizeDelegate(self)) self.view.setItemDelegateForColumn(self.Header.instances, NumericalDelegate(self)) self.view.setItemDelegateForColumn(self.Header.variables, NumericalDelegate(self)) self.view.resizeColumnToContents(self.Header.islocal) def _parse_info(self, file_path): if file_path in self.allinfo_remote: info = self.allinfo_remote[file_path] else: info = self.allinfo_local[file_path] islocal = file_path in self.allinfo_local isremote = file_path in self.allinfo_remote outdated = islocal and isremote and ( self.allinfo_remote[file_path].get('version', '') != self.allinfo_local[file_path].get('version', '')) islocal &= not outdated prefix = os.path.join('', *file_path[:-1]) filename = file_path[-1] return Namespace(file_path=file_path, prefix=prefix, filename=filename, islocal=islocal, outdated=outdated, **info) def create_model(self): allkeys = set(self.allinfo_local) | set(self.allinfo_remote) allkeys = sorted(allkeys) model = QStandardItemModel(self) model.setHorizontalHeaderLabels(self._header_labels) current_index = -1 for i, file_path in enumerate(allkeys): datainfo = self._parse_info(file_path) item1 = QStandardItem() item1.setData(" " if datainfo.islocal else "", Qt.DisplayRole) item1.setData(self.IndicatorBrushes[0], Qt.ForegroundRole) item1.setData(datainfo, Qt.UserRole) item2 = QStandardItem(datainfo.title) item3 = QStandardItem() item3.setData(datainfo.size, Qt.DisplayRole) item4 = QStandardItem() item4.setData(datainfo.instances, Qt.DisplayRole) item5 = QStandardItem() item5.setData(datainfo.variables, Qt.DisplayRole) item6 = QStandardItem() item6.setData(datainfo.target, Qt.DisplayRole) if datainfo.target: item6.setIcon(variable_icon(datainfo.target)) item7 = QStandardItem() item7.setData(", ".join(datainfo.tags) if datainfo.tags else "", Qt.DisplayRole) row = [item1, item2, item3, item4, item5, item6, item7] model.appendRow(row) if os.path.join(*file_path) == self.selected_id: current_index = i return model, current_index @Slot(object) def __set_index(self, f): # type: (Future) -> None # set results from `list_remote` query. assert QThread.currentThread() is self.thread() assert f.done() self.setBlocking(False) self.setStatusMessage("") self.allinfo_local = self.list_local() try: self.allinfo_remote = f.result() except Exception: # anytying can happen, pylint: disable=broad-except log.exception("Error while fetching updated index") if not self.allinfo_local: self.Error.no_remote_datasets() else: self.Warning.only_local_datasets() self.allinfo_remote = {} model, current_index = self.create_model() self.view.model().setSourceModel(model) self.view.selectionModel().selectionChanged.connect( self.__on_selection) self.view.resizeColumnToContents(0) self.view.setColumnWidth( 1, min(self.view.sizeHintForColumn(1), self.view.fontMetrics().width("X" * 37))) header = self.view.header() header.restoreState(self.header_state) if current_index != -1: selmodel = self.view.selectionModel() selmodel.select( self.view.model().mapFromSource(model.index(current_index, 0)), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) self.commit() def __update_cached_state(self): model = self.view.model().sourceModel() localinfo = self.list_local() assert isinstance(model, QStandardItemModel) allinfo = [] for i in range(model.rowCount()): item = model.item(i, 0) info = item.data(Qt.UserRole) is_local = info.file_path in localinfo is_current = (is_local and os.path.join( self.local_cache_path, *info.file_path) == self.current_output) item.setData(" " * (is_local + is_current), Qt.DisplayRole) item.setData(self.IndicatorBrushes[is_current], Qt.ForegroundRole) allinfo.append(info) def selected_dataset(self): """ Return the current selected dataset info or None if not selected Returns ------- info : Optional[Namespace] """ rows = self.view.selectionModel().selectedRows(0) assert 0 <= len(rows) <= 1 current = rows[0] if rows else None # type: Optional[QModelIndex] if current is not None: info = current.data(Qt.UserRole) assert isinstance(info, Namespace) else: info = None return info def filter(self): filter_string = self.filterLineEdit.text().strip() proxyModel = self.view.model() if proxyModel: proxyModel.setFilterFixedString(filter_string) def __on_selection(self): # Main datasets view selection has changed rows = self.view.selectionModel().selectedRows(0) assert 0 <= len(rows) <= 1 current = rows[0] if rows else None # type: Optional[QModelIndex] if current is not None: current = self.view.model().mapToSource(current) di = current.data(Qt.UserRole) text = description_html(di) self.descriptionlabel.setText(text) self.selected_id = os.path.join(di.prefix, di.filename) else: self.descriptionlabel.setText("") self.selected_id = None def commit(self): """ Commit a dataset to the output immediately (if available locally) or schedule download background and an eventual send. During the download the widget is in blocking state (OWWidget.isBlocking) """ di = self.selected_dataset() if di is not None: self.Error.clear() if self.__awaiting_state is not None: # disconnect from the __commit_complete self.__awaiting_state.watcher.done.disconnect( self.__commit_complete) # .. and connect to update_cached_state # self.__awaiting_state.watcher.done.connect( # self.__update_cached_state) # TODO: There are possible pending __progress_advance queued self.__awaiting_state.pb.advance.disconnect( self.__progress_advance) self.progressBarFinished() self.__awaiting_state = None if not di.islocal: pr = progress() callback = lambda pr=pr: pr.advance.emit() pr.advance.connect(self.__progress_advance, Qt.QueuedConnection) self.progressBarInit() self.setStatusMessage("Fetching...") self.setBlocking(True) f = self._executor.submit(ensure_local, self.INDEX_URL, di.file_path, self.local_cache_path, force=di.outdated, progress_advance=callback) w = FutureWatcher(f, parent=self) w.done.connect(self.__commit_complete) self.__awaiting_state = _FetchState(f, w, pr) else: self.setStatusMessage("") self.setBlocking(False) self.commit_cached(di.file_path) else: self.load_and_output(None) @Slot(object) def __commit_complete(self, f): # complete the commit operation after the required file has been # downloaded assert QThread.currentThread() is self.thread() assert self.__awaiting_state is not None assert self.__awaiting_state.future is f if self.isBlocking(): self.progressBarFinished() self.setBlocking(False) self.setStatusMessage("") self.__awaiting_state = None try: path = f.result() # anything can happen here, pylint: disable=broad-except except Exception as ex: log.exception("Error:") self.error(format_exception(ex)) path = None self.load_and_output(path) def commit_cached(self, file_path): path = LocalFiles(self.local_cache_path).localpath(*file_path) self.load_and_output(path) @Slot() def __progress_advance(self): assert QThread.currentThread() is self.thread() self.progressBarAdvance(1) def onDeleteWidget(self): super().onDeleteWidget() if self.__awaiting_state is not None: self.__awaiting_state.watcher.done.disconnect( self.__commit_complete) self.__awaiting_state.pb.advance.disconnect( self.__progress_advance) self.__awaiting_state = None @staticmethod def sizeHint(): return QSize(1100, 500) def closeEvent(self, event): self.splitter_state = bytes(self.splitter.saveState()) self.header_state = bytes(self.view.header().saveState()) super().closeEvent(event) def load_and_output(self, path): if path is None: self.Outputs.data.send(None) else: data = self.load_data(path) self.Outputs.data.send(data) self.current_output = path self.__update_cached_state() @staticmethod def load_data(path): return Orange.data.Table(path) def list_remote(self): # type: () -> Dict[Tuple[str, ...], dict] client = ServerFiles(server=self.INDEX_URL) return client.allinfo() def list_local(self): # type: () -> Dict[Tuple[str, ...], dict] return LocalFiles(self.local_cache_path).allinfo()
def __init__(self): super().__init__() self.backend = None self.data_desc_table = None self.database_desc = None vbox = gui.vBox(self.controlArea, "服务器", addSpace=True) box = gui.vBox(vbox) self.backends = BackendModel(Backend.available_backends()) self.backendcombo = QComboBox(box) if len(self.backends): self.backendcombo.setModel(self.backends) else: self.Error.no_backends() box.setEnabled(False) box.layout().addWidget(self.backendcombo) self.servertext = QLineEdit(box) self.servertext.setPlaceholderText('服务器') self.servertext.setToolTip('服务器') self.servertext.editingFinished.connect(self._load_credentials) if self.host: self.servertext.setText(self.host if not self.port else '{}:{}'.format(self.host, self.port)) box.layout().addWidget(self.servertext) self.databasetext = QLineEdit(box) self.databasetext.setPlaceholderText('Database[/Schema]') self.databasetext.setToolTip('Database or optionally Database/Schema') if self.database: self.databasetext.setText( self.database if not self.schema else '{}/{}'.format(self.database, self.schema)) box.layout().addWidget(self.databasetext) self.usernametext = QLineEdit(box) self.usernametext.setPlaceholderText('用户名称') self.usernametext.setToolTip('用户名称') box.layout().addWidget(self.usernametext) self.passwordtext = QLineEdit(box) self.passwordtext.setPlaceholderText('密码') self.passwordtext.setToolTip('密码') self.passwordtext.setEchoMode(QLineEdit.Password) box.layout().addWidget(self.passwordtext) self._load_credentials() self.tables = TableModel() tables = gui.hBox(box) self.tablecombo = QComboBox( minimumContentsLength=35, sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLength ) self.tablecombo.setModel(self.tables) self.tablecombo.setToolTip('表') tables.layout().addWidget(self.tablecombo) index = self.tablecombo.findText(str(self.table)) if index != -1: self.tablecombo.setCurrentIndex(index) # set up the callback to select_table in case of selection change self.tablecombo.activated[int].connect(self.select_table) self.connectbutton = gui.button( tables, self, '↻', callback=self.connect) self.connectbutton.setSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) tables.layout().addWidget(self.connectbutton) self.custom_sql = gui.vBox(box) self.custom_sql.setVisible(False) self.sqltext = QTextEdit(self.custom_sql) self.sqltext.setPlainText(self.sql) self.custom_sql.layout().addWidget(self.sqltext) mt = gui.hBox(self.custom_sql) cb = gui.checkBox(mt, self, 'materialize', 'Materialize to table ') cb.setToolTip('Save results of the query in a table') le = gui.lineEdit(mt, self, 'materialize_table_name') le.setToolTip('Save results of the query in a table') self.executebtn = gui.button( self.custom_sql, self, 'Execute', callback=self.open_table) box.layout().addWidget(self.custom_sql) gui.checkBox(box, self, "guess_values", "自动发现分类变量", callback=self.open_table) self.downloadcb = gui.checkBox(box, self, "download", "将数据下载到本地内存", callback=self.open_table) gui.rubber(self.buttonsArea) self.connect() QTimer.singleShot(0, self.select_table)
def __init__(self): super().__init__() self.controlArea = QWidget(self.controlArea) self.layout().addWidget(self.controlArea) layout = QGridLayout() self.controlArea.setLayout(layout) layout.setContentsMargins(4, 4, 4, 4) box = gui.vBox(self.controlArea, "Available Variables", addToLayout=False) self.filter_edit = QLineEdit() self.filter_edit.setToolTip("Filter the list of available variables.") box.layout().addWidget(self.filter_edit) if hasattr(self.filter_edit, "setPlaceholderText"): self.filter_edit.setPlaceholderText("Filter") self.completer = QCompleter() self.completer.setCompletionMode(QCompleter.InlineCompletion) self.completer_model = QStringListModel() self.completer.setModel(self.completer_model) self.completer.setModelSorting( QCompleter.CaseSensitivelySortedModel) self.filter_edit.setCompleter(self.completer) self.completer_navigator = CompleterNavigator(self) self.filter_edit.installEventFilter(self.completer_navigator) def dropcompleted(action): if action == Qt.MoveAction: self.commit() self.available_attrs = VariableListModel(enable_dnd=True) self.available_attrs_proxy = VariableFilterProxyModel() self.available_attrs_proxy.setSourceModel(self.available_attrs) self.available_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.available_attrs_view.setModel(self.available_attrs_proxy) aa = self.available_attrs aa.dataChanged.connect(self.update_completer_model) aa.rowsInserted.connect(self.update_completer_model) aa.rowsRemoved.connect(self.update_completer_model) self.available_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.available_attrs_view)) self.available_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.filter_edit.textChanged.connect(self.update_completer_prefix) self.filter_edit.textChanged.connect( self.available_attrs_proxy.set_filter_string) box.layout().addWidget(self.available_attrs_view) layout.addWidget(box, 0, 0, 3, 1) box = gui.vBox(self.controlArea, "Features", addToLayout=False) self.used_attrs = VariableListModel(enable_dnd=True) self.used_attrs_view = VariablesListItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.used_attrs_view.setModel(self.used_attrs) self.used_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.used_attrs_view)) self.used_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.used_attrs_view) layout.addWidget(box, 0, 2, 1, 1) box = gui.vBox(self.controlArea, "Target Variable", addToLayout=False) self.class_attrs = ClassVarListItemModel(enable_dnd=True) self.class_attrs_view = ClassVariableItemView( acceptedType=(Orange.data.DiscreteVariable, Orange.data.ContinuousVariable)) self.class_attrs_view.setModel(self.class_attrs) self.class_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.class_attrs_view)) self.class_attrs_view.dragDropActionDidComplete.connect(dropcompleted) self.class_attrs_view.setMaximumHeight(24) box.layout().addWidget(self.class_attrs_view) layout.addWidget(box, 1, 2, 1, 1) box = gui.vBox(self.controlArea, "Meta Attributes", addToLayout=False) self.meta_attrs = VariableListModel(enable_dnd=True) self.meta_attrs_view = VariablesListItemView( acceptedType=Orange.data.Variable) self.meta_attrs_view.setModel(self.meta_attrs) self.meta_attrs_view.selectionModel().selectionChanged.connect( partial(self.update_interface_state, self.meta_attrs_view)) self.meta_attrs_view.dragDropActionDidComplete.connect(dropcompleted) box.layout().addWidget(self.meta_attrs_view) layout.addWidget(box, 2, 2, 1, 1) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 0, 1, 1, 1) self.up_attr_button = gui.button(bbox, self, "Up", callback=partial(self.move_up, self.used_attrs_view)) self.move_attr_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.used_attrs_view)) self.down_attr_button = gui.button(bbox, self, "Down", callback=partial(self.move_down, self.used_attrs_view)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 1, 1, 1, 1) self.move_class_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.class_attrs_view, exclusive=True)) bbox = gui.vBox(self.controlArea, addToLayout=False, margin=0) layout.addWidget(bbox, 2, 1, 1, 1) self.up_meta_button = gui.button(bbox, self, "Up", callback=partial(self.move_up, self.meta_attrs_view)) self.move_meta_button = gui.button(bbox, self, ">", callback=partial(self.move_selected, self.meta_attrs_view)) self.down_meta_button = gui.button(bbox, self, "Down", callback=partial(self.move_down, self.meta_attrs_view)) autobox = gui.auto_commit(None, self, "auto_commit", "Send") layout.addWidget(autobox, 3, 0, 1, 3) reset = gui.button(None, self, "Reset", callback=self.reset) autobox.layout().insertWidget(0, self.report_button) autobox.layout().insertWidget(1, reset) autobox.layout().insertSpacing(2, 10) reset.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.report_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) layout.setRowStretch(0, 4) layout.setRowStretch(1, 0) layout.setRowStretch(2, 2) layout.setHorizontalSpacing(0) self.controlArea.setLayout(layout) self.data = None self.output_data = None self.original_completer_items = [] self.resize(500, 600)
class ListViewSearch(QListView): """ An QListView with an implicit and transparent row filtering. """ def __init__(self, *a, preferred_size=None, **ak): super().__init__(*a, **ak) self.__search = QLineEdit(self, placeholderText="Filter...") self.__search.textEdited.connect(self.__setFilterString) # Use an QSortFilterProxyModel for filtering. Note that this is # never set on the view, only its rows insertes/removed signals are # connected to observe an update row hidden state. self.__pmodel = QSortFilterProxyModel( self, filterCaseSensitivity=Qt.CaseInsensitive) self.__pmodel.rowsAboutToBeRemoved.connect( self.__filter_rowsAboutToBeRemoved) self.__pmodel.rowsInserted.connect(self.__filter_rowsInserted) self.__layout() self.preferred_size = preferred_size self.setMinimumHeight(100) def setFilterPlaceholderText(self, text: str): self.__search.setPlaceholderText(text) def filterPlaceholderText(self) -> str: return self.__search.placeholderText() def setFilterProxyModel(self, proxy: QSortFilterProxyModel) -> None: """ Set an instance of QSortFilterProxyModel that will be used for filtering the model. The `proxy` must be a filtering proxy only; it MUST not sort the row of the model. The FilterListView takes ownership of the proxy. """ self.__pmodel.rowsAboutToBeRemoved.disconnect( self.__filter_rowsAboutToBeRemoved) self.__pmodel.rowsInserted.disconnect(self.__filter_rowsInserted) self.__pmodel = proxy proxy.setParent(self) self.__pmodel.rowsAboutToBeRemoved.connect( self.__filter_rowsAboutToBeRemoved) self.__pmodel.rowsInserted.connect(self.__filter_rowsInserted) self.__pmodel.setSourceModel(self.model()) self.__filter_reset() def filterProxyModel(self) -> QSortFilterProxyModel: return self.__pmodel def setModel(self, model: QAbstractItemModel) -> None: super().setModel(model) self.__pmodel.setSourceModel(model) self.__filter_reset() self.model().rowsInserted.connect(self.__model_rowInserted) def setRootIndex(self, index: QModelIndex) -> None: super().setRootIndex(index) self.__filter_reset() def __filter_reset(self): root = self.rootIndex() self.__filter(range(self.__pmodel.rowCount(root))) def __setFilterString(self, string: str): self.__pmodel.setFilterFixedString(string) def setFilterString(self, string: str): """Set the filter string.""" self.__search.setText(string) self.__pmodel.setFilterFixedString(string) def filterString(self): """Return the filter string.""" return self.__search.text() def __filter(self, rows: Iterable[int]) -> None: """Set hidden state for rows based on filter string""" root = self.rootIndex() pm = self.__pmodel for r in rows: self.setRowHidden(r, not pm.filterAcceptsRow(r, root)) def __filter_set(self, rows: Iterable[int], state: bool): for r in rows: self.setRowHidden(r, state) def __filter_rowsAboutToBeRemoved(self, parent: QModelIndex, start: int, end: int) -> None: fmodel = self.__pmodel mrange = QItemSelection(fmodel.index(start, 0, parent), fmodel.index(end, 0, parent)) mranges = fmodel.mapSelectionToSource(mrange) for mrange in mranges: self.__filter_set(range(mrange.top(), mrange.bottom() + 1), True) def __filter_rowsInserted(self, parent: QModelIndex, start: int, end: int) -> None: fmodel = self.__pmodel mrange = QItemSelection(fmodel.index(start, 0, parent), fmodel.index(end, 0, parent)) mranges = fmodel.mapSelectionToSource(mrange) for mrange in mranges: self.__filter_set(range(mrange.top(), mrange.bottom() + 1), False) def __model_rowInserted(self, _, start: int, end: int) -> None: """ Filter elements when inserted in list - proxy model's rowsAboutToBeRemoved is not called on elements that are hidden when inserting """ self.__filter(range(start, end + 1)) def resizeEvent(self, event: QResizeEvent) -> None: super().resizeEvent(event) def updateGeometries(self) -> None: super().updateGeometries() self.__layout() def __layout(self): margins = self.viewportMargins() search = self.__search sh = search.sizeHint() size = self.size() margins.setTop(sh.height()) vscroll = self.verticalScrollBar() style = self.style() transient = style.styleHint(QStyle.SH_ScrollBar_Transient, None, vscroll) w = size.width() if vscroll.isVisibleTo(self) and not transient: w = w - vscroll.width() - 1 search.setGeometry(0, 0, w, sh.height()) self.setViewportMargins(margins) def sizeHint(self): return (self.preferred_size if self.preferred_size is not None else super().sizeHint())
def _setup_gui_name(self): self.name_edit = QLineEdit() self.main_form.addRow("Name:", self.name_edit) self.name_edit.editingFinished.connect(self.on_name_changed)
class ORACLESQL(OWWidget): name = "Oracle SQL" icon = "icons/OracleSQL.svg" want_main_area = True want_message_bar = True #inputs = [] outputs = [("Data", Table)] description = "Select data from oracle databases" settingsHandler = settings.DomainContextHandler() autocommit = settings.Setting(False, schema_only=True) savedQuery = settings.Setting(None, schema_only=True) savedUsername = settings.Setting(None, schema_only=True) savedPwd = settings.Setting(None, schema_only=True) savedDB = settings.Setting(None, schema_only=True) class Error(OWWidget.Error): no_backends = Msg("Please install cx_Oracle package. It is either missing or not working properly") class Outputs: data = Output("Data", Table) def __init__(self): super().__init__() #Defaults self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self.domain = None self.data = None self.query = '' if self.savedQuery is not None: self.query = self.savedQuery self.username = '' if self.savedUsername is not None: self.username = self.savedUsername self.password = '' if self.savedPwd is not None: self.password = self.savedPwd self.database = '' if self.savedDB is not None: self.database = self.savedDB #Control Area layout sip.delete(self.controlArea.layout()) self.controlArea.setLayout(QHBoxLayout()) self.connectBox = gui.widgetBox(self.controlArea, "Database connection") self.connectBox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.sqlBox = gui.widgetBox(self.controlArea, "SQL") #Database self.userLabel = gui.label(self.connectBox, self, 'User name') self.connectUser = QLineEdit(self.username, self) self.connectBox.layout().addWidget(self.connectUser) self.passwordLabel = gui.label(self.connectBox, self, 'Password') self.connectPassword = QLineEdit(self.password, self) self.connectPassword.setEchoMode(QLineEdit.Password) self.connectBox.layout().addWidget(self.connectPassword) self.dbLabel = gui.label(self.connectBox, self, 'Database') self.connectDB = QLineEdit(self.database, self) self.connectBox.layout().addWidget(self.connectDB) self.runSQL = gui.auto_commit(self.connectBox, self, 'autocommit', label='Run SQL', commit=self.commit) # query self.queryTextEdit = QPlainTextEdit(self.query, self) self.sqlBox.layout().addWidget(self.queryTextEdit) if self.autocommit: self.commit() def handleNewSignals(self): self._invalidate() def countUniques(self,lst): return len(set([x for x in lst if x is not None])) def setOfUniques(self,lst): return sorted(set([x for x in lst if x is not None])) def dateToStr(self,lst): return([str(x) if x is not None else x for x in lst ]) def commit(self): if cx_Oracle is None: data = [] columns = [] self.Error.no_backends() username = None password = None database = None query = None else: username = self.connectUser.text() password = self.connectPassword.text() database = self.connectDB.text() con = cx_Oracle.connect(username+"/"+password+"@"+database) query = self.queryTextEdit.toPlainText() cur = con.cursor() cur.execute(query) data = cur.fetchall() columns = [i[0] for i in cur.description] data_tr=list(zip(*data)) n=len(columns) featurelist=[ContinuousVariable(str(columns[col])) if all(type(x)==int or type(x)==float or type(x)==type(None) for x in data_tr[col]) else TimeVariable(str(columns[col])) if all(type(x)==type(datetime.datetime(9999,12,31,0,0)) or type(x)==type(None) for x in data_tr[col]) else DiscreteVariable(str(columns[col]),self.setOfUniques(data_tr[col])) if self.countUniques(data_tr[col]) < 101 else DiscreteVariable(str(columns[col]),self.setOfUniques(data_tr[col])) for col in range (0,n)] data_tr = [self.dateToStr(data_tr[col]) if all(type(x)==type(datetime.datetime(9999,12,31,0,0)) or type(x)==type(None) for x in data_tr[col]) else data_tr[col] for col in range (0,n)] data=list(zip(*data_tr)) orangedomain=Domain(featurelist) orangetable=Table(orangedomain,data) self.Outputs.data.send(orangetable) self.savedQuery = query self.savedUsername = username self.savedPwd = password self.savedDB = database def _invalidate(self): self.commit()