class AddTracUI(QWidget): def __init__(self, parent): super(AddTracUI, self).__init__() self.title = 'Create TracControl Element' self.setLayout(QVBoxLayout(self)) self.parent = parent self.trac = Trac(name='', outputPin=0, enabled=False, icon=Config.icon('tracControl', 'rearDiff'), momentary=False) self._nameControl = LineEdit('Name', self) self._nameControl.kb.connect(self.showOSK) self._outputPinControlLabel = QLabel('Output Pin', self) self._outputPinControl = QComboBox(self) for _pin in self.parent.availablePins(): self._outputPinControl.addItem(str(_pin)) self._outputPinControl.setCurrentIndex(self._outputPinControl.findText(str(self.trac.outputPin))) self._enabledControl = QCheckBox('Enabled', self) self._iconControlLabel = QLabel('Icon', self) self._iconControl = QComboBox(self) for _key in Config.icons['tracControl'].keys(): icon = Config.icon('tracControl', _key) self._iconControl.addItem(icon['name'], _key) self._iconControl.setItemIcon(self._iconControl.count() - 1, QIcon(icon['path'])) self._addTracBtn = QPushButton('Add TracControl Element', self) self._addTracBtn.clicked.connect(self.__createTracBtnAction) self._cancelBtn = QPushButton('Cancel', self) self._cancelBtn.clicked.connect(self.__cancel) _layout = [ ['_nameControl'], ['_outputPinControlLabel', '_outputPinControl'], ['_enabledControl'], ['_iconControlLabel', '_iconControl'], ['_addTracBtn', '_cancelBtn'] ] for _list in _layout: _panel = QWidget(self) _panel.setLayout(QHBoxLayout(_panel)) _panel.layout().setAlignment(Qt.AlignCenter) for _control in _list: _panel.layout().addWidget(eval('self.%s' % _control)) self.layout().addWidget(_panel) def __createTracBtnAction(self): self.trac.name = self._nameControl.text() self.trac.outputPin = int(self._outputPinControl.currentText()) self.trac.enabled = self._enabledControl.isChecked() self.trac.icon = self._iconControl.currentData() self.parent.tracs.addTrac(self.trac) self.parent.tracs.save() self.parent.loadUI('config_trac') self.parent.enableConfigButtons() def __cancel(self): self.parent.loadUI('config_trac') self.parent.enableConfigButtons() def showOSK(self): self.window().dock.show() self.window().osk.rWidget = self._nameControl
class OWLoadData(widget.OWWidget): name = "Load Data" icon = "icons/upload.svg" priority = 10 class Outputs: data = widget.Output("Data", Orange.data.Table) class Information(widget.OWWidget.Information): modified = widget.Msg( "Uncommited changes\nPress 'Load data' to submit changes") class Warning(widget.OWWidget.Warning): sampling_in_effect = widget.Msg("Sampling is in effect.") class Error(widget.OWWidget.Error): row_annotation_mismatch = widget.Msg("Row annotation length mismatch\n" "Expected {} rows got {}") col_annotation_mismatch = widget.Msg( "Column annotation length mismatch\n" "Expected {} rows got {}") _recent = settings.Setting([]) # type: List[str] _recent_row_annotations = settings.Setting([]) # type: List[str] _recent_col_annotations = settings.Setting([]) # type: List[str] _cells_in_rows = settings.Setting(False) _col_annotations_enabled = settings.Setting(False) _row_annotations_enabled = settings.Setting(False) _last_path = settings.Setting("") # type: str _header_rows_count = settings.Setting(1) # type: int _header_cols_count = settings.Setting(1) # type: int _sample_rows_enabled = settings.Setting(False) # type: bool _sample_cols_enabled = settings.Setting(False) # type: bool _sample_cols_p = settings.Setting(10.0) # type: bool _sample_rows_p = settings.Setting(10.0) # type: bool settingsHandler = RunaroundSettingsHandler() want_main_area = False resizing_enabled = False def __init__(self): super().__init__() self._current_path = "" icon_open_dir = self.style().standardIcon(QStyle.SP_DirOpenIcon) # Top grid with file selection combo box grid = QGridLayout() lb = QLabel("File:") lb.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.recent_combo = cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=20, toolTip="Select a recent file") self.recent_model = cb.model() # type: QStandardItemModel self.recent_combo.activated[int].connect(self._select_recent) browse = QPushButton("...", autoDefault=False, icon=icon_open_dir, clicked=self.browse) # reload = QPushButton("Reload", autoDefault=False, icon=icon_reload) grid.addWidget(lb, 0, 0, Qt.AlignVCenter) grid.addWidget(cb, 0, 1) grid.addWidget(browse, 0, 2) # grid.addWidget(reload, 0, 3) self.summary_label = label = QLabel("", self) label.ensurePolished() f = label.font() if f.pointSizeF() != -1: f.setPointSizeF(f.pointSizeF() * 5 / 6) else: f.setPixelSize(f.pixelSize() * 5 / 6) label.setFont(f) grid.addWidget(label, 1, 1, 1, 3) self.controlArea.layout().addLayout(grid) box = gui.widgetBox(self.controlArea, "Headers and Row Labels", spacing=-1) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) self.header_rows_spin = spin = QSpinBox(box, minimum=0, maximum=3, value=self._header_rows_count, keyboardTracking=False) spin.valueChanged.connect(self.set_header_rows_count) hl.addWidget(QLabel("Data starts with", box)) hl.addWidget(self.header_rows_spin) hl.addWidget(QLabel("header row(s)", box)) hl.addStretch(10) box.layout().addLayout(hl) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) self.header_cols_spin = spin = QSpinBox(box, minimum=0, maximum=3, value=self._header_cols_count, keyboardTracking=False) spin.valueChanged.connect(self.set_header_cols_count) hl.addWidget(QLabel("First", box)) hl.addWidget(self.header_cols_spin) hl.addWidget(QLabel("column(s) are row labels", box)) hl.addStretch(10) box.layout().addLayout(hl) self.data_struct_box = box = gui.widgetBox(self.controlArea, "Input Data Structure") gui.radioButtons(box, self, "_cells_in_rows", [ "Genes in rows, samples in columns", "Samples in rows, genes in columns" ], callback=self._invalidate) box = gui.widgetBox(self.controlArea, "Sample Data", spacing=-1) grid = QGridLayout() grid.setContentsMargins(0, 0, 0, 0) box.layout().addLayout(grid) self.sample_rows_cb = cb = QCheckBox(checked=self._sample_rows_enabled) spin = QSpinBox(minimum=0, maximum=100, value=self._sample_rows_p, enabled=self._sample_rows_enabled) spin.valueChanged.connect(self.set_sample_rows_p) suffix = QLabel("% of Samples", enabled=self._sample_rows_enabled) cb.toggled.connect(self.set_sample_rows_enabled) cb.toggled.connect(spin.setEnabled) cb.toggled.connect(suffix.setEnabled) grid.addWidget(cb, 0, 0) grid.addWidget(spin, 0, 1) grid.addWidget(suffix, 0, 2) self.sample_cols_cb = cb = QCheckBox(checked=self._sample_cols_enabled) spin = QSpinBox(minimum=0, maximum=100, value=self._sample_cols_p, enabled=self._sample_cols_enabled) spin.valueChanged.connect(self.set_sample_cols_p) suffix = QLabel("% of genes", enabled=self._sample_cols_enabled) cb.toggled.connect(self.set_sample_cols_enabled) cb.toggled.connect(spin.setEnabled) cb.toggled.connect(suffix.setEnabled) grid.addWidget(cb, 1, 0) grid.addWidget(spin, 1, 1) grid.addWidget(suffix, 1, 2) grid.setColumnStretch(3, 10) self.annotation_files_box = box = gui.widgetBox( self.controlArea, "Cell && Gene Annotation Files") form = QFormLayout( formAlignment=Qt.AlignLeft, rowWrapPolicy=QFormLayout.WrapAllRows, ) box.layout().addLayout(form) self.row_annotations_cb = cb = QCheckBox( "Cell annotations", checked=self._row_annotations_enabled) self._row_annotations_w = w = QWidget( enabled=self._row_annotations_enabled) cb.toggled.connect(self.set_row_annotations_enabled) cb.toggled.connect(w.setEnabled) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) w.setLayout(hl) self.row_annotations_combo = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=18) self.row_annotations_combo.activated.connect(self._invalidate) hl.addWidget(self.row_annotations_combo) hl.addWidget( QPushButton("...", box, autoDefault=False, icon=icon_open_dir, clicked=self.browse_row_annotations)) # hl.addWidget(QPushButton("Reload", box, autoDefault=False, # icon=icon_reload)) form.addRow(cb, w) self.col_annotations_cb = cb = QCheckBox( "Gene annotations", checked=self._col_annotations_enabled) self._col_annotations_w = w = QWidget( enabled=self._col_annotations_enabled) cb.toggled.connect(self.set_col_annotations_enabled) cb.toggled.connect(w.setEnabled) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) w.setLayout(hl) self.col_annotations_combo = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=18) self.col_annotations_combo.activated.connect(self._invalidate) hl.addWidget(self.col_annotations_combo) hl.addWidget( QPushButton("...", box, autoDefault=False, icon=icon_open_dir, clicked=self.browse_col_annotations)) # hl.addWidget(QPushButton("Reload", box, autoDefault=False, # icon=icon_reload)) form.addRow(cb, w) self.controlArea.layout().addStretch(10) self.load_data_button = button = VariableTextPushButton( "Load data", autoDefault=True, textChoiceList=["Load data", "Reload"]) self.load_data_button.setAutoDefault(True) button.clicked.connect(self.commit, Qt.QueuedConnection) self.controlArea.layout().addWidget(button, alignment=Qt.AlignRight) init_recent_paths_model( self.recent_model, [RecentPath.create(p, []) for p in self._recent], ) init_recent_paths_model( self.row_annotations_combo.model(), [RecentPath.create(p, []) for p in self._recent_row_annotations]) init_recent_paths_model( self.col_annotations_combo.model(), [RecentPath.create(p, []) for p in self._recent_col_annotations]) self._update_summary() self._update_warning() if self._last_path != "" and os.path.exists(self._last_path): self.set_current_path(self._last_path) else: self.recent_combo.setCurrentIndex(-1) def _update_warning(self): if (self._sample_rows_enabled and self._sample_rows_p < 100) or \ (self._sample_cols_enabled and self._sample_cols_p < 100): self.Warning.sampling_in_effect() else: self.Warning.sampling_in_effect.clear() def set_sample_rows_enabled(self, enabled): if self._sample_rows_enabled != enabled: self._sample_rows_enabled = enabled self._update_warning() self._invalidate() def set_sample_cols_enabled(self, enabled): if self._sample_cols_enabled != enabled: self._sample_cols_enabled = enabled self._update_warning() self._invalidate() def set_sample_rows_p(self, p): if self._sample_rows_p != p: self._sample_rows_p = p self._update_warning() self._invalidate() def set_sample_cols_p(self, p): if self._sample_cols_p != p: self._sample_cols_p = p self._update_warning() self._invalidate() def set_header_rows_count(self, n): if self._header_rows_count != n: self._header_rows_count = n self.header_rows_spin.setValue(n) self._invalidate() def set_header_cols_count(self, n): if self._header_cols_count != n: self._header_cols_count = n self.header_cols_spin.setValue(n) self._invalidate() def set_row_annotations_enabled(self, enabled): if self._row_annotations_enabled != enabled: self._row_annotations_enabled = enabled self.row_annotations_cb.setChecked(enabled) self._invalidate() def set_col_annotations_enabled(self, enabled): if self._col_annotations_enabled != enabled: self._col_annotations_enabled = enabled self.col_annotations_cb.setChecked(enabled) self._invalidate() def set_current_path(self, path): if samepath(self._current_path, path): return model = self.recent_model index = -1 pathitem = None for i in range(model.rowCount()): item = model.item(i) data = item.data(Qt.UserRole) if item is not None else None if isinstance(data, RecentPath) and samepath(path, data.abspath): index, pathitem = i, data break rpaths = [] if pathitem is None: assert index == -1 pathitem = RecentPath.create(path, rpaths) if index != -1: item = model.takeRow(index) else: item = RecentPath_asqstandarditem(pathitem) opts = infer_options(path) if path.endswith(".count"): self.set_header_rows_count(1) self.set_header_cols_count(1) fixed_format = False elif path.endswith(".mtx"): self.set_header_rows_count(0) self.set_header_cols_count(0) fixed_format = False self._cells_in_rows = False else: fixed_format = True if opts.transposed is not None: self._cells_in_rows = not opts.transposed self.data_struct_box.setEnabled(fixed_format) self.header_rows_spin.setEnabled(fixed_format) self.header_cols_spin.setEnabled(fixed_format) model.insertRow(0, item) self._current_path = path self.recent_combo.setCurrentIndex(0) self._update_summary() if opts.row_annotation_file is not None: index = insert_recent_path( self.row_annotations_combo.model(), RecentPath.create(opts.row_annotation_file, [])) self.row_annotations_combo.setCurrentIndex(index) self.set_row_annotations_enabled(True) else: self.row_annotations_combo.setCurrentIndex(-1) if opts.column_annotation_file is not None: index = insert_recent_path( self.col_annotations_combo.model(), RecentPath.create(opts.column_annotation_file, [])) self.col_annotations_combo.setCurrentIndex(index) self.set_col_annotations_enabled(True) else: self.col_annotations_combo.setCurrentIndex(-1) if path.endswith(".mtx") \ and opts.row_annotation_file is not None \ and opts.column_annotation_file is not None \ and os.path.basename(opts.row_annotation_file) == "barcodes.tsv" \ and os.path.basename(opts.column_annotation_file) == "genes.tsv": # 10x gene-barcode matrix # TODO: The genes/barcodes files should be unconditionally loaded # alongside the mtx. The row/col annotations might be used to # specify additional sources. For the time being they are put in # the corresponding comboboxes and made uneditable. self.annotation_files_box.setEnabled(False) else: self.annotation_files_box.setEnabled(True) self._invalidate() def _update_summary(self): path = self._current_path size = None ncols = None nrows = None try: st = os.stat(path) except OSError: pass else: size = st.st_size if os.path.splitext(path)[1] == ".mtx": try: with open(path, "rb") as f: nrows, ncols = scipy.io.mminfo(f)[:2] except OSError: pass except ValueError: pass else: try: with open(path, "rt", encoding="latin-1") as f: sep = separator_from_filename(path) ncols = len(next(csv.reader(f, delimiter=sep))) nrows = sum(1 for _ in f) except OSError: pass except StopIteration: pass text = [] if size is not None: text += [sizeformat(size)] if nrows is not None: text += ["{:n} rows".format(nrows)] if nrows is not None: text += ["{:n} columns".format(ncols)] self.summary_label.setText(", ".join(text)) def current_path(self): return self._current_path def _select_recent(self, index): # type: (int) -> None # select a file from the recent list (entered via combo box `activate`) assert 0 <= index < self.recent_model.rowCount() item = self.recent_model.item(index) pathitem = item.data(Qt.UserRole) assert isinstance(pathitem, RecentPath) self.set_current_path(pathitem.abspath) @Slot() def browse(self): dlg = QFileDialog(self) dlg.setAcceptMode(QFileDialog.AcceptOpen) dlg.setFileMode(QFileDialog.ExistingFile) filters = Formats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: f = dlg.selectedNameFilter() filename = dlg.selectedFiles()[0] self.set_current_path(filename) @Slot() def browse_row_annotations(self): dlg = QFileDialog(self, acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile) filters = AnnotationFormats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: f = dlg.selectedNameFilter() filename = dlg.selectedFiles()[0] m = self.row_annotations_combo.model() # type: QStandardItemModel pathitem = RecentPath.create(filename, []) index = insert_recent_path(m, pathitem) self.row_annotations_combo.setCurrentIndex(index) self._invalidate() @Slot() def browse_col_annotations(self): dlg = QFileDialog(self, acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile) filters = AnnotationFormats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: f = dlg.selectedNameFilter() filename = dlg.selectedFiles()[0] m = self.col_annotations_combo.model() # type: QStandardItemModel pathitem = RecentPath.create(filename, []) index = insert_recent_path(m, pathitem) self.col_annotations_combo.setCurrentIndex(index) self._invalidate() def _invalidate(self): self.set_modified(True) def set_modified(self, modified): if modified: text = "Load data" else: text = "Reload" self.load_data_button.setText(text) self.load_data_button.setAutoDefault(modified) # Setting autoDefault once also sets default which persists even after # settings autoDefault back to False?? self.load_data_button.setDefault(modified) self.Information.modified(shown=modified) def commit(self): path = self._current_path if not path: return transpose = not self._cells_in_rows row_annot = self.row_annotations_combo.currentData(Qt.UserRole) col_annot = self.col_annotations_combo.currentData(Qt.UserRole) if self._row_annotations_enabled and \ isinstance(row_annot, RecentPath) and \ os.path.exists(row_annot.abspath): row_annot = row_annot.abspath # type: str else: row_annot = None if self._col_annotations_enabled and \ isinstance(col_annot, RecentPath) and \ os.path.exists(col_annot.abspath): col_annot = col_annot.abspath # type: str else: col_annot = None meta_parts = [] # type: List[pd.DataFrame] attrs = [] # type: List[ContinuousVariable] metas = [] # type: List[StringVariable] rstate = np.random.RandomState(0x667) skip_row = skip_col = None if self._sample_cols_enabled: p = self._sample_cols_p if p < 100: def skip_col(i, p=p): return i > 3 and rstate.uniform(0, 100) > p if self._sample_rows_enabled: p = self._sample_rows_p if p < 100: def skip_row(i, p=p): return i > 3 and rstate.uniform(0, 100) > p header_rows = self._header_rows_count header_rows_indices = [] if header_rows == 0: header_rows = None elif header_rows == 1: header_rows = 0 header_rows_indices = [0] else: header_rows = list(range(header_rows)) header_rows_indices = header_rows header_cols = self._header_cols_count header_cols_indices = [] if header_cols == 0: header_cols = None elif header_cols == 1: header_cols = 0 header_cols_indices = [0] else: header_cols = list(range(header_cols)) header_cols_indices = header_cols if transpose: _skip_row, _skip_col = skip_col, skip_row else: _skip_col, _skip_row = skip_col, skip_row _userows = _usecols = None userows_mask = usecols_mask = None if _skip_col is not None: ncols = pd.read_csv(path, sep=separator_from_filename(path), index_col=None, nrows=1).shape[1] usecols_mask = np.array([ not _skip_col(i) or i in header_cols_indices for i in range(ncols) ], dtype=bool) _usecols = np.flatnonzero(usecols_mask) if _skip_row is not None: userows_mask = [] # record the used rows def _skip_row(i, test=_skip_row): r = test(i) userows_mask.append(r) return r meta_df_index = None row_annot_header = 0 row_annot_columns = None col_annot_header = 0 col_annot_columns = None if os.path.splitext(path)[1] == ".mtx": # 10x cellranger output X = scipy.io.mmread(path) assert isinstance(X, scipy.sparse.coo_matrix) if transpose: X = X.T if _skip_row is not None: userows_mask = np.array( [not _skip_row(i) for i in range(X.shape[0])]) X = X.tocsr()[np.flatnonzero(userows_mask)] if _skip_col is not None: usecols_mask = np.array( [not _skip_col(i) for i in range(X.shape[1])]) X = X.tocsc()[:, np.flatnonzero(usecols_mask)] X = X.todense(order="F") if userows_mask is not None: meta_df = pd.DataFrame({}, index=np.flatnonzero(userows_mask)) else: meta_df = pd.DataFrame({}, index=pd.RangeIndex(X.shape[0])) meta_df_index = meta_df.index row_annot_header = None row_annot_columns = ["Barcodes"] col_annot_header = None col_annot_columns = ["Id", "Gene"] leading_cols = leading_rows = 0 else: df = pd.read_csv(path, sep=separator_from_filename(path), index_col=header_cols, header=header_rows, skiprows=_skip_row, usecols=_usecols) if _skip_row is not None: userows_mask = np.array(userows_mask, dtype=bool) if transpose: df = df.transpose() userows_mask, usecols_mask = usecols_mask, userows_mask leading_rows = len(header_cols_indices) leading_cols = len(header_rows_indices) else: leading_rows = len(header_rows_indices) leading_cols = len(header_cols_indices) X = df.values attrs = [ContinuousVariable.make(str(g)) for g in df.columns] meta_df = df.iloc[:, :0] # Take the index # type: pd.DataFrame meta_df_index = df.index meta_parts = (meta_df, ) self.Error.row_annotation_mismatch.clear() self.Error.col_annotation_mismatch.clear() if row_annot is not None: row_annot_df = pd.read_csv(row_annot, sep=separator_from_filename(row_annot), header=row_annot_header, names=row_annot_columns, index_col=None) if userows_mask is not None: # NOTE: we account for column header/ row index expected = len(userows_mask) - leading_rows else: expected = X.shape[0] if len(row_annot_df) != expected: self.Error.row_annotation_mismatch(expected, len(row_annot_df)) row_annot_df = None if row_annot_df is not None and userows_mask is not None: # use the same sample indices indices = np.flatnonzero(userows_mask[leading_rows:]) row_annot_df = row_annot_df.iloc[indices] # if path.endswith(".count") and row_annot.endswith('.meta'): # assert np.all(row_annot_df.iloc[:, 0] == df.index) if row_annot_df is not None and meta_df_index is not None: # Try to match the leading columns with the meta_df_index. # If found then drop the columns (or index if the level does # not have a name but the annotation col does) drop_cols = [] drop_index_level = [] for i in range(meta_df_index.nlevels): meta_df_level = meta_df_index.get_level_values(i) if np.all(row_annot_df.iloc[:, i] == meta_df_level): if meta_df_level.name is None: drop_index_level.append(i) elif meta_df_level.name == row_annot_df.columns[ i].name: drop_cols.append(i) if drop_cols: row_annot_df = row_annot_df.drop(columns=drop_cols) if drop_index_level: for i in reversed(drop_index_level): if isinstance(meta_df.index, pd.MultiIndex): meta_df_index = meta_df_index.droplevel(i) else: assert i == 0 meta_df_index = pd.RangeIndex(meta_df_index.size) meta_df = pd.DataFrame({}, index=meta_df_index) if row_annot_df is not None: meta_parts = (meta_df, row_annot_df) if col_annot is not None: col_annot_df = pd.read_csv(col_annot, sep=separator_from_filename(col_annot), header=col_annot_header, names=col_annot_columns, index_col=None) if usecols_mask is not None: expected = len(usecols_mask) - leading_cols else: expected = X.shape[1] if len(col_annot_df) != expected: self.Error.col_annotation_mismatch(expected, len(col_annot_df)) col_annot_df = None if col_annot_df is not None and usecols_mask is not None: indices = np.flatnonzero(usecols_mask[leading_cols:]) col_annot_df = col_annot_df.iloc[indices] if col_annot_df is not None: assert len(col_annot_df) == X.shape[1] if not attrs and X.shape[1]: # No column names yet attrs = [ ContinuousVariable.make(str(v)) for v in col_annot_df.iloc[:, 0] ] names = [str(c) for c in col_annot_df.columns] for var, values in zip(attrs, col_annot_df.values): var.attributes.update( {n: v for n, v in zip(names, values)}) if meta_parts: meta_parts = [ df_.reset_index() if not df_.index.is_integer() else df_ for df_ in meta_parts ] metas = [ StringVariable.make(name) for name in chain(*(_.columns for _ in meta_parts)) ] M = np.hstack(tuple(df_.values for df_ in meta_parts)) else: metas = None M = None if not attrs and X.shape[1]: attrs = Orange.data.Domain.from_numpy(X).attributes domain = Orange.data.Domain(attrs, metas=metas) d = Orange.data.Table.from_numpy(domain, X, None, M) self.Outputs.data.send(d) self.set_modified(False) def onDeleteWidget(self): super().onDeleteWidget() def _saveState(self): maxitems = 15 def dataiter(model, role=Qt.UserRole): return (model.data(model.index(i, 0), role) for i in range(model.rowCount())) def recent_paths(model): return [ el.abspath for el in dataiter(model) if isinstance(el, RecentPath) ][:maxitems] self._recent = recent_paths(self.recent_model) self._recent_row_annotations = recent_paths( self.row_annotations_combo.model()) self._recent_col_annotations = recent_paths( self.col_annotations_combo.model()) self._last_path = self._current_path def saveSettings(self): self._saveState() super().saveSettings()
class EditTracUI(QWidget): def __init__(self, trac, parent): super(EditTracUI, self).__init__() self.title = 'Edit TracControl Element' self.setLayout(QVBoxLayout(self)) self.layout().setAlignment(Qt.AlignCenter) self.parent = parent self.trac = trac # Init controls self._nameControl = LineEdit('Name', self) self._nameControl.setText(self.trac.name) self._nameControl.kb.connect(self.showOSK) self._outputPinControlLabel = QLabel('Output Pin', self) self._outputPinControl = QComboBox(self) for _pins in self.parent.availablePins(): self._outputPinControl.addItem(str(_pins)) for _i in range(self._outputPinControl.count()): if self._outputPinControl.itemText(_i) == str(self.trac.outputPin): self._outputPinControl.setCurrentIndex(_i) break self._enabledControl = QCheckBox('Enabled', self) self._enabledControl.setChecked(self.trac.enabled) self._iconControlLabel = QLabel('Icon Path', self) self._iconControl = QComboBox(self) for _key in Config.icons['tracControl'].keys(): icon = Config.icon('tracControl', _key) self._iconControl.addItem(icon['name'], _key) self._iconControl.setItemIcon(self._iconControl.count() - 1, QIcon(icon['path'])) # Set combobox selection to icon variable for iconIdx in range(self._iconControl.count()): if self.trac.icon is not None and self._iconControl.itemData( iconIdx) == self.trac.icon: self._iconControl.setCurrentIndex(iconIdx) break self._saveBtn = QPushButton('Save', self) self._saveBtn.clicked.connect(self.__saveBtnAction) self._cancelBtn = QPushButton('Cancel', self) self._cancelBtn.clicked.connect(self.__cancel) _layout = [['_nameControl'], ['_outputPinControlLabel', '_outputPinControl'], ['_enabledControl'], ['_iconControlLabel', '_iconControl'], ['_saveBtn', '_cancelBtn']] for _list in _layout: _panel = QWidget(self) _panel.setLayout(QHBoxLayout(_panel)) _panel.layout().setAlignment(Qt.AlignCenter) for _ctrl in _list: _panel.layout().addWidget(eval('self.%s' % _ctrl)) self.layout().addWidget(_panel) def __saveBtnAction(self): self.trac.name = self._nameControl.text() self.trac.outputPin = int(self._outputPinControl.currentText()) self.trac.enabled = self._enabledControl.isChecked() self.trac.icon = self._iconControl.currentData() self.parent.tracs.save() self.parent.loadUI('config_trac') def __cancel(self): self.parent.loadUI('config_trac') def showOSK(self): self.window().dock.show() self.window().osk.rWidget = self._nameControl
class OWLoadData(widget.OWWidget): name = "" icon = "icons/LoadData.svg" priority = 110 class Outputs: data = widget.Output("Data", Table) class Information(widget.OWWidget.Information): modified = widget.Msg( "Uncommited changes\nPress 'Load data' to submit changes") class Warning(widget.OWWidget.Warning): sampling_in_effect = widget.Msg("Sampling is in effect.") class Error(widget.OWWidget.Error): row_annotation_mismatch = widget.Msg("Row annotation length mismatch\n" "Expected {} rows got {}") col_annotation_mismatch = widget.Msg( "Column annotation length mismatch\n" "Expected {} rows got {}") inadequate_headers = widget.Msg("Headers and Row Labels error") reading_error = widget.Msg("Cannot read data using given parameters.") _recent = settings.Setting([]) # type: List[RecentPath] _recent_row_annotations = settings.Setting([]) # type: List[RecentPath] _recent_col_annotations = settings.Setting([]) # type: List[RecentPath] _cells_in_rows = settings.Setting(False) _col_annotations_enabled = settings.Setting(False) _row_annotations_enabled = settings.Setting(False) _last_path = settings.Setting("") # type: str _header_rows_count = settings.Setting(1) # type: int _header_cols_count = settings.Setting(1) # type: int _sample_rows_enabled = settings.Setting(False) # type: bool _sample_cols_enabled = settings.Setting(False) # type: bool _sample_cols_p = settings.Setting(10.0) # type: bool _sample_rows_p = settings.Setting(10.0) # type: bool settingsHandler = RunaroundSettingsHandler() want_main_area = False resizing_enabled = False cells_in_rows_changed = Signal() def __init__(self): super().__init__() self._current_path = "" self._data_loader = Loader() icon_open_dir = self.style().standardIcon(QStyle.SP_DirOpenIcon) # Top grid with file selection combo box self.file_layout = grid = QGridLayout() lb = QLabel("File:") lb.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.recent_combo = cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=20, toolTip="Select a recent file") self.recent_model = cb.model() # type: QStandardItemModel self.recent_combo.activated[int].connect(self._select_recent) browse = QPushButton("...", autoDefault=False, icon=icon_open_dir, clicked=self.browse) # reload = QPushButton("Reload", autoDefault=False, icon=icon_reload) grid.addWidget(lb, 0, 0, Qt.AlignVCenter) grid.addWidget(cb, 0, 1) grid.addWidget(browse, 0, 2) # grid.addWidget(reload, 0, 3) self.summary_label = label = QLabel("", self) label.ensurePolished() f = label.font() if f.pointSizeF() != -1: f.setPointSizeF(f.pointSizeF() * 5 / 6) else: f.setPixelSize(f.pixelSize() * 5 / 6) label.setFont(f) grid.addWidget(label, 1, 1, 1, 3) self.controlArea.layout().addLayout(grid) box = gui.widgetBox(self.controlArea, "Headers and Row Labels", spacing=-1) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) self.header_rows_spin = spin = QSpinBox(box, minimum=0, maximum=3, value=self._header_rows_count, keyboardTracking=False) spin.valueChanged.connect(self.set_header_rows_count) hl.addWidget(QLabel("Data starts with", box)) hl.addWidget(self.header_rows_spin) hl.addWidget(QLabel("header row(s)", box)) hl.addStretch(10) box.layout().addLayout(hl) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) self.header_cols_spin = spin = QSpinBox(box, minimum=0, maximum=3, value=self._header_cols_count, keyboardTracking=False) spin.valueChanged.connect(self.set_header_cols_count) hl.addWidget(QLabel("First", box)) hl.addWidget(self.header_cols_spin) hl.addWidget(QLabel("column(s) are row labels", box)) hl.addStretch(10) box.layout().addLayout(hl) self.data_struct_box = box = gui.widgetBox(self.controlArea, "Input Data Structure") gui.radioButtons(box, self, "_cells_in_rows", [ "Genes in rows, cells in columns", "Cells in rows, genes in columns" ], callback=self._cells_in_rows_changed) box = gui.widgetBox(self.controlArea, "Sample Data", spacing=-1) grid = QGridLayout() grid.setContentsMargins(0, 0, 0, 0) box.layout().addLayout(grid) self.sample_rows_cb = cb = QCheckBox(checked=self._sample_rows_enabled) self.sample_rows_p_spin = spin = QSpinBox(minimum=0, maximum=100, value=self._sample_rows_p) spin.valueChanged.connect(self.set_sample_rows_p) suffix = QLabel("% of cells") cb.toggled.connect(self.set_sample_rows_enabled) grid.addWidget(cb, 0, 0) grid.addWidget(spin, 0, 1) grid.addWidget(suffix, 0, 2) self.sample_cols_cb = cb = QCheckBox(checked=self._sample_cols_enabled) self.sample_cols_p_spin = spin = QSpinBox(minimum=0, maximum=100, value=self._sample_cols_p) spin.valueChanged.connect(self.set_sample_cols_p) suffix = QLabel("% of genes") cb.toggled.connect(self.set_sample_cols_enabled) grid.addWidget(cb, 1, 0) grid.addWidget(spin, 1, 1) grid.addWidget(suffix, 1, 2) grid.setColumnStretch(3, 10) self.annotation_files_box = box = gui.widgetBox( self.controlArea, "Cell && Gene Annotation Files") form = QFormLayout( formAlignment=Qt.AlignLeft, rowWrapPolicy=QFormLayout.WrapAllRows, ) box.layout().addLayout(form) self.row_annotations_cb = cb = QCheckBox( "Cell annotations", checked=self._row_annotations_enabled) self._row_annotations_w = w = QWidget( enabled=self._row_annotations_enabled) cb.toggled.connect(self.set_row_annotations_enabled) cb.toggled.connect(w.setEnabled) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) w.setLayout(hl) self.row_annotations_combo = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=18) self.row_annotations_combo.activated.connect( self._row_annotations_combo_changed) hl.addWidget(self.row_annotations_combo) hl.addWidget( QPushButton("...", box, autoDefault=False, icon=icon_open_dir, clicked=self.browse_row_annotations)) # hl.addWidget(QPushButton("Reload", box, autoDefault=False, # icon=icon_reload)) form.addRow(cb, w) self.col_annotations_cb = cb = QCheckBox( "Gene annotations", checked=self._col_annotations_enabled) self._col_annotations_w = w = QWidget( enabled=self._col_annotations_enabled) cb.toggled.connect(self.set_col_annotations_enabled) cb.toggled.connect(w.setEnabled) hl = QHBoxLayout() hl.setContentsMargins(0, 0, 0, 0) w.setLayout(hl) self.col_annotations_combo = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=18) self.col_annotations_combo.activated.connect( self._col_annotations_combo_changed) hl.addWidget(self.col_annotations_combo) hl.addWidget( QPushButton("...", box, autoDefault=False, icon=icon_open_dir, clicked=self.browse_col_annotations)) # hl.addWidget(QPushButton("Reload", box, autoDefault=False, # icon=icon_reload)) form.addRow(cb, w) self.controlArea.layout().addStretch(10) self.load_data_button = button = VariableTextPushButton( "Load data", autoDefault=True, textChoiceList=["Load data", "Reload"]) self.load_data_button.setAutoDefault(True) button.clicked.connect(self.commit, Qt.QueuedConnection) self.controlArea.layout().addWidget(button, alignment=Qt.AlignRight) init_recent_paths_model( self.recent_model, [self.resolve_path(p) for p in self._recent], ) init_recent_paths_model( self.row_annotations_combo.model(), [self.resolve_path(p) for p in self._recent_row_annotations]) init_recent_paths_model( self.col_annotations_combo.model(), [self.resolve_path(p) for p in self._recent_col_annotations]) self._update_summary() self._update_warning() if self._last_path != "" and os.path.exists(self._last_path): QTimer.singleShot(0, lambda: self.set_current_path(self._last_path)) else: self.recent_combo.setCurrentIndex(-1) def resolve_path(self, path): basedir = self.workflowEnv().get("basedir", None) if not basedir or not path: return path return path.resolve([("basedir", basedir)]) or path def _cells_in_rows_changed(self): self._data_loader.transposed = not self._cells_in_rows self._invalidate() self.cells_in_rows_changed.emit() def _row_annotations_combo_changed(self): path = self.row_annotations_combo.currentData(Qt.UserRole) if isinstance(path, RecentPath) and os.path.exists(path.abspath): self._data_loader.row_annotation_file = path # type: RecentPath else: self._data_loader.row_annotation_file = None self._invalidate() def _col_annotations_combo_changed(self): path = self.col_annotations_combo.currentData(Qt.UserRole) if isinstance(path, RecentPath) and os.path.exists(path.abspath): self._data_loader.col_annotation_file = path # type: RecentPath else: self._data_loader.col_annotation_file = None self._invalidate() def _update_warning(self): if (self._sample_rows_enabled and self._sample_rows_p < 100) or \ (self._sample_cols_enabled and self._sample_cols_p < 100): self.Warning.sampling_in_effect() else: self.Warning.sampling_in_effect.clear() def set_sample_rows_enabled(self, enabled, commit=True): if self._sample_rows_enabled != enabled: self._sample_rows_enabled = enabled self.sample_rows_cb.setChecked(enabled) self._update_warning() self._data_loader.sample_rows_enabled = enabled if commit: self._invalidate() def set_sample_cols_enabled(self, enabled, commit=True): if self._sample_cols_enabled != enabled: self._sample_cols_enabled = enabled self.sample_cols_cb.setChecked(enabled) self._update_warning() self._data_loader.sample_cols_enabled = enabled if commit: self._invalidate() def set_sample_rows_p(self, p, commit=True): if self._sample_rows_p != p: self._sample_rows_p = p self._update_warning() self.sample_rows_p_spin.setValue(p) self._data_loader.sample_rows_p = p if commit: self._invalidate() def set_sample_cols_p(self, p, commit=True): if self._sample_cols_p != p: self._sample_cols_p = p self._update_warning() self.sample_cols_p_spin.setValue(p) self._data_loader.sample_cols_p = p if commit: self._invalidate() def set_header_rows_count(self, n, commit=True): if self._header_rows_count != n: self._header_rows_count = n self.header_rows_spin.setValue(n) self._data_loader.header_rows_count = n if commit: self._invalidate() def set_header_cols_count(self, n, commit=True): if self._header_cols_count != n: self._header_cols_count = n self.header_cols_spin.setValue(n) self._data_loader.header_cols_count = n if commit: self._invalidate() def set_row_annotations_enabled(self, enabled, commit=True): if self._row_annotations_enabled != enabled: self._row_annotations_enabled = enabled self.row_annotations_cb.setChecked(enabled) self._data_loader.row_annotations_enabled = enabled if commit: self._invalidate() def set_col_annotations_enabled(self, enabled, commit=True): if self._col_annotations_enabled != enabled: self._col_annotations_enabled = enabled self.col_annotations_cb.setChecked(enabled) self._data_loader.col_annotations_enabled = enabled if commit: self._invalidate() def set_current_path(self, path): if samepath(self._current_path, path): return model = self.recent_model index = -1 pathitem = None for i in range(model.rowCount()): item = model.item(i) data = item.data(Qt.UserRole) if item is not None else None if isinstance(data, RecentPath) and samepath(path, data.abspath): index, pathitem = i, data break rpaths = [] if pathitem is None: assert index == -1 pathitem = RecentPath.create(path, rpaths) if index != -1: item = model.takeRow(index) else: item = RecentPath_asqstandarditem(pathitem) model.insertRow(0, item) self._current_path = path self.recent_combo.setCurrentIndex(0) self._data_loader = get_data_loader(path) self._update_summary() self.setup_gui() self._invalidate() def setup_gui(self): """ Use loader predefined values. If the value is None, set loader's parameter to widget's setting value. """ loader = self._data_loader if loader.header_rows_count is not None: self.set_header_rows_count(loader.header_rows_count, False) else: loader.header_rows_count = self._header_rows_count if loader.header_cols_count is not None: self.set_header_cols_count(loader.header_cols_count, False) else: loader.header_cols_count = self._header_cols_count if loader.transposed is not None: self._cells_in_rows = not loader.transposed else: loader.transposed = not self._cells_in_rows if loader.sample_rows_enabled is not None: self.set_sample_rows_enabled(loader.sample_rows_enabled, False) else: loader.sample_rows_enabled = self._sample_rows_enabled if loader.sample_cols_enabled is not None: self.set_sample_cols_enabled(loader.sample_cols_enabled, False) else: loader.sample_cols_enabled = self._sample_cols_enabled if loader.sample_rows_p is not None: self.set_sample_rows_p(loader.sample_rows_p, False) else: loader.sample_rows_p = self._sample_rows_p if loader.sample_cols_p is not None: self.set_sample_cols_p(loader.sample_cols_p, False) else: loader.sample_cols_p = self._sample_cols_p if loader.row_annotation_file is not None: index = insert_recent_path( self.row_annotations_combo.model(), self.resolve_path(loader.row_annotation_file)) self.row_annotations_combo.setCurrentIndex(index) self.set_row_annotations_enabled(loader.row_annotations_enabled, False) else: self.row_annotations_combo.setCurrentIndex(-1) self.set_row_annotations_enabled(False, False) if loader.col_annotation_file is not None: index = insert_recent_path( self.col_annotations_combo.model(), self.resolve_path(loader.col_annotation_file)) self.col_annotations_combo.setCurrentIndex(index) self.set_col_annotations_enabled(loader.col_annotations_enabled, False) else: self.col_annotations_combo.setCurrentIndex(-1) self.set_col_annotations_enabled(False, False) self.header_rows_spin.setEnabled(loader.FIXED_FORMAT) self.header_cols_spin.setEnabled(loader.FIXED_FORMAT) self.data_struct_box.setEnabled(loader.FIXED_FORMAT) self.annotation_files_box.setEnabled(loader.ENABLE_ANNOTATIONS) def _update_summary(self): size = self._data_loader.file_size ncols = self._data_loader.n_cols nrows = self._data_loader.n_rows text = [] if size is not None: text += [sizeformat(size)] if nrows is not None: text += ["{:n} rows".format(nrows)] if nrows is not None: text += ["{:n} columns".format(ncols)] self.summary_label.setText(", ".join(text)) def current_path(self): return self._current_path def _select_recent(self, index): # type: (int) -> None # select a file from the recent list (entered via combo box `activate`) assert 0 <= index < self.recent_model.rowCount() item = self.recent_model.item(index) pathitem = item.data(Qt.UserRole) assert isinstance(pathitem, RecentPath) self.set_current_path(pathitem.abspath) @Slot() def browse(self): dlg = QFileDialog(self) dlg.setAcceptMode(QFileDialog.AcceptOpen) dlg.setFileMode(QFileDialog.ExistingFile) filters = Formats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: filename = dlg.selectedFiles()[0] self.set_current_path(filename) @Slot() def browse_row_annotations(self): dlg = QFileDialog(self, acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile) filters = AnnotationFormats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: filename = dlg.selectedFiles()[0] m = self.row_annotations_combo.model() # type: QStandardItemModel pathitem = RecentPath.create(filename, []) index = insert_recent_path(m, pathitem) self.row_annotations_combo.setCurrentIndex(index) self._invalidate() @Slot() def browse_col_annotations(self): dlg = QFileDialog(self, acceptMode=QFileDialog.AcceptOpen, fileMode=QFileDialog.ExistingFile) filters = AnnotationFormats dlg.setNameFilters(filters) if filters: dlg.selectNameFilter(filters[0]) if dlg.exec_() == QFileDialog.Accepted: filename = dlg.selectedFiles()[0] m = self.col_annotations_combo.model() # type: QStandardItemModel pathitem = RecentPath.create(filename, []) index = insert_recent_path(m, pathitem) self.col_annotations_combo.setCurrentIndex(index) self._invalidate() def _invalidate(self): self.set_modified(True) def set_modified(self, modified): if modified: text = "Load data" else: text = "Reload" self.load_data_button.setText(text) self.load_data_button.setAutoDefault(modified) # Setting autoDefault once also sets default which persists even after # settings autoDefault back to False?? self.load_data_button.setDefault(modified) self.Information.modified(shown=modified) def commit(self): path = self._current_path if not path: return self.Outputs.data.send(self._data_loader()) self.show_error_messages() self.set_modified(False) def show_error_messages(self): self.Error.row_annotation_mismatch.clear() self.Error.col_annotation_mismatch.clear() self.Error.inadequate_headers.clear() errors = self._data_loader.errors if len(errors["row_annot_mismatch"]): self.Error.row_annotation_mismatch(*errors["row_annot_mismatch"]) if len(errors["col_annot_mismatch"]): self.Error.col_annotation_mismatch(*errors["col_annot_mismatch"]) if len(errors["inadequate_headers"]): self.Error.inadequate_headers(*errors["inadequate_headers"]) if len(errors["reading_error"]): self.Error.reading_error() def onDeleteWidget(self): super().onDeleteWidget() def _saveState(self): maxitems = 15 def dataiter(model, role=Qt.UserRole): return (model.data(model.index(i, 0), role) for i in range(model.rowCount())) def recent_paths(model): return [ self.relocate_path(el) for el in dataiter(model) if isinstance(el, RecentPath) ][:maxitems] self._recent = recent_paths(self.recent_model) self._recent_row_annotations = recent_paths( self.row_annotations_combo.model()) self._recent_col_annotations = recent_paths( self.col_annotations_combo.model()) self._last_path = self._current_path def relocate_path(self, path): basedir = self.workflowEnv().get("basedir", None) if not basedir or not path: return path return RecentPath.create(path.abspath, [("basedir", basedir)]) def saveSettings(self): self._saveState() super().saveSettings()
class ColorGradientSelection(QWidget): activated = Signal(int) currentIndexChanged = Signal(int) thresholdsChanged = Signal(float, float) centerChanged = Signal(float) def __init__(self, *args, thresholds=(0.0, 1.0), center=None, **kwargs): super().__init__(*args, **kwargs) low = round(clip(thresholds[0], 0., 1.), 2) high = round(clip(thresholds[1], 0., 1.), 2) high = max(low, high) self.__threshold_low, self.__threshold_high = low, high self.__center = center form = QFormLayout(formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow) form.setContentsMargins(0, 0, 0, 0) self.gradient_cb = QComboBox( None, objectName="gradient-combo-box", ) self.gradient_cb.setAttribute(Qt.WA_LayoutUsesWidgetRect) icsize = self.style().pixelMetric(QStyle.PM_SmallIconSize, None, self.gradient_cb) self.gradient_cb.setIconSize(QSize(64, icsize)) model = itemmodels.ContinuousPalettesModel() model.setParent(self) self.gradient_cb.setModel(model) self.gradient_cb.activated[int].connect(self.activated) self.gradient_cb.currentIndexChanged.connect(self.currentIndexChanged) if center is not None: def __on_center_changed(): self.__center = float(self.center_edit.text() or "0") self.centerChanged.emit(self.__center) self.center_box = QWidget() center_layout = QHBoxLayout() self.center_box.setLayout(center_layout) width = QFontMetrics(self.font()).boundingRect("9999999").width() self.center_edit = QLineEdit(text=f"{self.__center}", maximumWidth=width, placeholderText="0", alignment=Qt.AlignRight) self.center_edit.setValidator(QDoubleValidator()) self.center_edit.editingFinished.connect(__on_center_changed) center_layout.setContentsMargins(0, 0, 0, 0) center_layout.addStretch(1) center_layout.addWidget(QLabel("Centered at")) center_layout.addWidget(self.center_edit) self.gradient_cb.currentIndexChanged.connect( self.__update_center_visibility) else: self.center_box = None slider_low = Slider(objectName="threshold-low-slider", minimum=0, maximum=100, value=int(low * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksBelow, pageStep=10, toolTip=self.tr("Low gradient threshold"), whatsThis=self.tr( "Applying a low threshold will squeeze the " "gradient from the lower end")) slider_high = Slider(objectName="threshold-low-slider", minimum=0, maximum=100, value=int(high * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksAbove, pageStep=10, toolTip=self.tr("High gradient threshold"), whatsThis=self.tr( "Applying a high threshold will squeeze the " "gradient from the higher end")) form.setWidget(0, QFormLayout.SpanningRole, self.gradient_cb) if self.center_box: form.setWidget(1, QFormLayout.SpanningRole, self.center_box) form.addRow(self.tr("Low:"), slider_low) form.addRow(self.tr("High:"), slider_high) self.slider_low = slider_low self.slider_high = slider_high self.slider_low.valueChanged.connect(self.__on_slider_low_moved) self.slider_high.valueChanged.connect(self.__on_slider_high_moved) self.setLayout(form) def setModel(self, model: QAbstractItemModel) -> None: self.gradient_cb.setModel(model) def model(self) -> QAbstractItemModel: return self.gradient_cb.model() def findData(self, data: Any, role: Qt.ItemDataRole) -> int: return self.gradient_cb.findData(data, role) def setCurrentIndex(self, index: int) -> None: self.gradient_cb.setCurrentIndex(index) self.__update_center_visibility() def currentIndex(self) -> int: return self.gradient_cb.currentIndex() currentIndex_ = Property(int, currentIndex, setCurrentIndex, notify=currentIndexChanged) def currentData(self, role=Qt.UserRole) -> Any: return self.gradient_cb.currentData(role) def thresholds(self) -> Tuple[float, float]: return self.__threshold_low, self.__threshold_high thresholds_ = Property(object, thresholds, notify=thresholdsChanged) def thresholdLow(self) -> float: return self.__threshold_low def setThresholdLow(self, low: float) -> None: self.setThresholds(low, max(self.__threshold_high, low)) thresholdLow_ = Property(float, thresholdLow, setThresholdLow, notify=thresholdsChanged) def thresholdHigh(self) -> float: return self.__threshold_high def setThresholdHigh(self, high: float) -> None: self.setThresholds(min(self.__threshold_low, high), high) def center(self) -> float: return self.__center def setCenter(self, center: float) -> None: self.__center = center self.center_edit.setText(f"{center}") self.centerChanged.emit(center) thresholdHigh_ = Property(float, thresholdLow, setThresholdLow, notify=thresholdsChanged) def __on_slider_low_moved(self, value: int) -> None: high = self.slider_high old = self.__threshold_low, self.__threshold_high self.__threshold_low = value / 100. if value >= high.value(): self.__threshold_high = value / 100. high.setSliderPosition(value) new = self.__threshold_low, self.__threshold_high if new != old: self.thresholdsChanged.emit(*new) def __on_slider_high_moved(self, value: int) -> None: low = self.slider_low old = self.__threshold_low, self.__threshold_high self.__threshold_high = value / 100. if low.value() >= value: self.__threshold_low = value / 100 low.setSliderPosition(value) new = self.__threshold_low, self.__threshold_high if new != old: self.thresholdsChanged.emit(*new) def setThresholds(self, low: float, high: float) -> None: low = round(clip(low, 0., 1.), 2) high = round(clip(high, 0., 1.), 2) if low > high: high = low if self.__threshold_low != low or self.__threshold_high != high: self.__threshold_high = high self.__threshold_low = low self.slider_low.setSliderPosition(low * 100) self.slider_high.setSliderPosition(high * 100) self.thresholdsChanged.emit(high, low) def __update_center_visibility(self): if self.center_box is None: return palette = self.currentData() self.center_box.setVisible( isinstance(palette, colorpalettes.Palette) and palette.flags & palette.Flags.Diverging != 0)
class ColorGradientSelection(QWidget): activated = Signal(int) currentIndexChanged = Signal(int) thresholdsChanged = Signal(float, float) def __init__(self, *args, thresholds=(0.0, 1.0), **kwargs): super().__init__(*args, **kwargs) low = round(clip(thresholds[0], 0., 1.), 2) high = round(clip(thresholds[1], 0., 1.), 2) high = max(low, high) self.__threshold_low, self.__threshold_high = low, high form = QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow ) form.setContentsMargins(0, 0, 0, 0) self.gradient_cb = QComboBox( None, objectName="gradient-combo-box", ) self.gradient_cb.setAttribute(Qt.WA_LayoutUsesWidgetRect) icsize = self.style().pixelMetric( QStyle.PM_SmallIconSize, None, self.gradient_cb ) self.gradient_cb.setIconSize(QSize(64, icsize)) model = itemmodels.ContinuousPalettesModel() model.setParent(self) self.gradient_cb.setModel(model) self.gradient_cb.activated[int].connect(self.activated) self.gradient_cb.currentIndexChanged.connect(self.currentIndexChanged) slider_low = QSlider( objectName="threshold-low-slider", minimum=0, maximum=100, value=int(low * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksBelow, pageStep=10, toolTip=self.tr("Low gradient threshold"), whatsThis=self.tr("Applying a low threshold will squeeze the " "gradient from the lower end") ) slider_high = QSlider( objectName="threshold-low-slider", minimum=0, maximum=100, value=int(high * 100), orientation=Qt.Horizontal, tickPosition=QSlider.TicksAbove, pageStep=10, toolTip=self.tr("High gradient threshold"), whatsThis=self.tr("Applying a high threshold will squeeze the " "gradient from the higher end") ) form.setWidget(0, QFormLayout.SpanningRole, self.gradient_cb) form.addRow(self.tr("Low:"), slider_low) form.addRow(self.tr("High:"), slider_high) self.slider_low = slider_low self.slider_high = slider_high self.slider_low.valueChanged.connect(self.__on_slider_low_moved) self.slider_high.valueChanged.connect(self.__on_slider_high_moved) self.setLayout(form) def setModel(self, model: QAbstractItemModel) -> None: self.gradient_cb.setModel(model) def model(self) -> QAbstractItemModel: return self.gradient_cb.model() def findData(self, data: Any, role: Qt.ItemDataRole) -> int: return self.gradient_cb.findData(data, role) def setCurrentIndex(self, index: int) -> None: self.gradient_cb.setCurrentIndex(index) def currentIndex(self) -> int: return self.gradient_cb.currentIndex() currentIndex_ = Property( int, currentIndex, setCurrentIndex, notify=currentIndexChanged) def currentData(self, role=Qt.UserRole) -> Any: return self.gradient_cb.currentData(role) def thresholds(self) -> Tuple[float, float]: return self.__threshold_low, self.__threshold_high thresholds_ = Property(object, thresholds, notify=thresholdsChanged) def thresholdLow(self) -> float: return self.__threshold_low def setThresholdLow(self, low: float) -> None: self.setThresholds(low, max(self.__threshold_high, low)) thresholdLow_ = Property( float, thresholdLow, setThresholdLow, notify=thresholdsChanged) def thresholdHigh(self) -> float: return self.__threshold_high def setThresholdHigh(self, high: float) -> None: self.setThresholds(min(self.__threshold_low, high), high) thresholdHigh_ = Property( float, thresholdLow, setThresholdLow, notify=thresholdsChanged) def __on_slider_low_moved(self, value: int) -> None: high = self.slider_high old = self.__threshold_low, self.__threshold_high self.__threshold_low = value / 100. if value >= high.value(): self.__threshold_high = value / 100. high.setSliderPosition(value) new = self.__threshold_low, self.__threshold_high if new != old: self.thresholdsChanged.emit(*new) def __on_slider_high_moved(self, value: int) -> None: low = self.slider_low old = self.__threshold_low, self.__threshold_high self.__threshold_high = value / 100. if low.value() >= value: self.__threshold_low = value / 100 low.setSliderPosition(value) new = self.__threshold_low, self.__threshold_high if new != old: self.thresholdsChanged.emit(*new) def setThresholds(self, low: float, high: float) -> None: low = round(clip(low, 0., 1.), 2) high = round(clip(high, 0., 1.), 2) if low > high: high = low if self.__threshold_low != low or self.__threshold_high != high: self.__threshold_high = high self.__threshold_low = low self.slider_low.setSliderPosition(low * 100) self.slider_high.setSliderPosition(high * 100) self.thresholdsChanged.emit(high, low)
class EditLightUI(QWidget): def __init__(self, light, parent): super(EditLightUI, self).__init__() self.title = 'Edit Lighting Element' self.parent = parent self.light = light self.setLayout(QVBoxLayout(self)) self.layout().setAlignment(Qt.AlignCenter) # Init Name text control self._nameControl = LineEdit('Name', self) self._nameControl.setText(self.light.name) self._nameControl.kb.connect(self.showOSK) # Init Output Pin dropdown control self._outputPinControlLabel = QLabel('Output Pin', self) self._outputPinControl = QComboBox(self) for x in self.parent.availablePins(): self._outputPinControl.addItem(str(x)) for i in range(self._outputPinControl.count()): if self._outputPinControl.itemText(i) == str(self.light.outputPin): self._outputPinControl.setCurrentIndex(i) break del x, i # Init Enabled checkbox control and set value self._enabledControl = QCheckBox('Enabled', self) self._enabledControl.setChecked(self.light.enabled) # Init Icon dropdown control self._iconControlLabel = QLabel('Icon Path', self) self._iconControl = QComboBox(self) for key in Config.icons['lights'].keys(): icon = Config.icon('lights', key) self._iconControl.addItem(icon['name'], key) self._iconControl.setItemIcon(self._iconControl.count() - 1, QIcon(icon['path'])) for i in range(self._iconControl.count()): if self.light.icon is not None and self._iconControl.itemData( i) == self.light.icon: self._iconControl.setCurrentIndex(i) break del key, i # Init Strobe checkbox control and set value self._strobeControl = QCheckBox('Strobe', self) self._strobeControl.setChecked(self.light.strobe) # Init Save button self._saveBtn = QPushButton('Save', self) self._saveBtn.clicked.connect(self.__saveBtnAction) # Init cancel button self._cancelBtn = QPushButton('Cancel', self) self._cancelBtn.clicked.connect(self.__cancel) # Assign control layout layoutList = [['_nameControl'], ['_outputPinControlLabel', '_outputPinControl'], ['_enabledControl', '_strobeControl'], ['_iconControlLabel', '_iconControl'], ['_saveBtn', '_cancelBtn']] for l in layoutList: panel = QWidget(self) panel.setLayout(QHBoxLayout(panel)) panel.layout().setAlignment(Qt.AlignCenter) for ctrl in l: panel.layout().addWidget(eval('self.%s' % ctrl)) self.layout().addWidget(panel) # Destroy local variables del l, ctrl def __saveBtnAction(self): self.light.name = self._nameControl.text() self.light.outputPin = int(self._outputPinControl.currentText()) self.light.enabled = self._enabledControl.isChecked() self.light.icon = self._iconControl.currentData() self.light.strobe = self._strobeControl.isChecked() self.parent.lights.save() self.parent.loadUI("config_light") def __cancel(self): self.parent.loadUI("config_light") def showOSK(self): self.window().dock.show() self.window().osk.rWidget = self._nameControl
class EditOBAUI(QWidget): def __init__(self, oba, parent): super(EditOBAUI, self).__init__() self.parent = parent self.oba = oba self.setLayout(QVBoxLayout(self)) self.layout().setAlignment(Qt.AlignCenter) # Init Name text control self._nameControl = LineEdit('Name', self) self._nameControl.setText(self.oba.name) self._nameControl.kb.connect(self.showOSK) # Init Output Pin dropdown control self._outputPinControlLabel = QLabel('Output Pin', self) self._outputPinControl = QComboBox(self) for _pin in self.parent.availablePins(): self._outputPinControl.addItem(str(_pin)) for _i in range(self._outputPinControl.count()): if self._outputPinControl.itemText(_i) == str(self.oba.outputPin): self._outputPinControl.setCurrentIndex(_i) break # Init Momentary checkbox control and set value self._momentaryControl = QCheckBox('Momentary', self) self._momentaryControl.setChecked(self.oba.momentary) # Init Enabled checkbox control and set value self._enabledControl = QCheckBox('Enabled', self) self._enabledControl.setChecked(self.oba.enabled) # Init Icon dropdown control self._iconControlLabel = QLabel('Icon Path', self) self._iconControl = QComboBox(self) for _key in Config.icons['oba'].keys(): icon = Config.icon('oba', _key) self._iconControl.addItem(icon['name'], _key) self._iconControl.setItemIcon(self._iconControl.count() - 1, QIcon(icon['path'])) for _i in range(self._iconControl.count()): # Set current index if matching icon attribute if self.oba.icon is not None and self._iconControl.itemData( _i) == self.oba.icon: self._iconControl.setCurrentIndex(_i) break # Init Save button self._saveBtn = QPushButton('Save', self) self._saveBtn.clicked.connect(self.__saveBtnAction) # Init cancel button self._cancelBtn = QPushButton('Cancel', self) self._cancelBtn.clicked.connect(self.__cancel) # Assign control layout _layout = [['_nameControl'], ['_outputPinControlLabel', '_outputPinControl'], ['_momentaryControl', '_enabledControl'], ['_iconControlLabel', '_iconControl'], ['_saveBtn', '_cancelBtn']] for _list in _layout: _panel = QWidget(self) _panel.setLayout(QHBoxLayout(_panel)) _panel.layout().setAlignment(Qt.AlignCenter) _panel.layout().setSpacing(20) for _control in _list: _panel.layout().addWidget(eval('self.%s' % _control)) self.layout().addWidget(_panel) def __saveBtnAction(self): self.oba.name = self._nameControl.text() self.oba.outputPin = int(self._outputPinControl.currentText()) self.oba.enabled = self._enabledControl.isChecked() self.oba.icon = self._iconControl.currentData() self.oba.momentary = self._momentaryControl.isChecked() self.parent.obas.save() self.parent.loadUI('config_oba') def __cancel(self): self.parent.loadUI('config_oba') def showOSK(self): self.window().dock.show() self.window().osk.rWidget = self._nameControl