class IMCWidget(QWidget): NAME = 'Imaging Mass Cytometry' FULL_NAME = f'napari-imc: {NAME}' AREA = 'right' ALLOWED_AREAS = ['left', 'right'] def __init__(self, napari_viewer: Viewer, parent: Optional[QWidget] = None): super(IMCWidget, self).__init__(parent) self._controller = IMCController(napari_viewer, self) self._imc_file_tree_model = IMCFileTreeModel(self._controller, parent=self) self._imc_file_tree_view = IMCFileTreeView(parent=self) self._imc_file_tree_view.setModel(self._imc_file_tree_model) self._channel_table_model = ChannelTableModel(self._controller, parent=self) self._channel_table_proxy_model = QSortFilterProxyModel(self) self._channel_table_proxy_model.setSourceModel( self._channel_table_model) self._channel_table_proxy_model.sort( self._channel_table_model.LABEL_COLUMN) self._channel_table_view = ChannelTableView(parent=self) self._channel_table_view.setModel(self._channel_table_proxy_model) self._channel_controls_widget = ChannelControlsWidget(self._controller, parent=self) self._channel_controls_container = QStackedWidget(self) self._channel_controls_container.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum) self._channel_controls_container.addWidget(QWidget(self)) self._channel_controls_container.addWidget( self._channel_controls_widget) layout = QVBoxLayout(self) splitter = QSplitter(Qt.Vertical, self) splitter.addWidget(self._imc_file_tree_view) channel_panel = QWidget(self) channel_panel_layout = QVBoxLayout() channel_panel_layout.addWidget(self._channel_table_view) channel_panel_layout.addWidget(self._channel_controls_container) channel_panel.setLayout(channel_panel_layout) splitter.addWidget(channel_panel) layout.addWidget(splitter) self.setLayout(layout) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._imc_file_tree_model.dataChanged.connect def on_imc_file_tree_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): item = top_left.internalPointer() if isinstance(item, IMCFilePanoramaModel): if item.is_shown: self._controller.hide_imc_file_panorama(item) else: self._controller.show_imc_file_panorama(item) elif isinstance(item, IMCFileAcquisitionModel): if item.is_loaded: self._controller.unload_imc_file_acquisition(item) else: self._controller.load_imc_file_acquisition(item) # noinspection PyUnresolvedReferences @self._imc_file_tree_view.events.imc_file_closed.connect def on_imc_file_tree_view_imc_file_closed(imc_file: IMCFileModel): self._controller.close_imc_file(imc_file) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @self._channel_table_model.dataChanged.connect def on_channel_table_model_data_changed(top_left: QModelIndex, bottom_right: QModelIndex, roles: Optional[int] = None): channel = self._controller.channels[top_left.row()] if channel.is_shown: self._controller.hide_channel(channel) else: self._controller.show_channel(channel) channel_table_selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) # noinspection PyUnusedLocal # noinspection PyUnresolvedReferences @channel_table_selection_model.selectionChanged.connect def on_channel_table_view_selection_changed( selected: QItemSelection, deselected: QItemSelection): selected_channels = [] for index in self._channel_table_view.selectedIndexes(): index = self._channel_table_proxy_model.mapToSource(index) channel = self._controller.channels[index.row()] selected_channels.append(channel) self._controller.selected_channels = selected_channels def select_channel(self, channel_index: int): top_left = self._channel_table_model.index(channel_index, 0) top_left = self._channel_table_proxy_model.mapFromSource(top_left) bottom_right = self._channel_table_model.index( channel_index, self._channel_table_model.columnCount() - 1) bottom_right = self._channel_table_proxy_model.mapFromSource( bottom_right) selection_model: QItemSelectionModel = self._channel_table_view.selectionModel( ) selection_model.clearSelection( ) # first clear the selection, to make sure the channel controls refresh selection_model.select(QItemSelection(top_left, bottom_right), QItemSelectionModel.Select) def refresh_channel_controls_widget(self): self._channel_controls_widget.refresh() if len(self._controller.selected_channels) > 0: self._channel_controls_container.setCurrentIndex(1) else: self._channel_controls_container.setCurrentIndex(0) @property def controller(self): return self._controller @property def imc_file_tree_model(self) -> IMCFileTreeModel: return self._imc_file_tree_model @property def channel_table_model(self) -> ChannelTableModel: return self._channel_table_model
class RedisTree(QMainWindow): addKey = Signal(object) currentChanged = Signal(object) def __init__(self, redis, parent=None): super(RedisTree, self).__init__(parent) self.load_ui() ui = self.ui self.redis = redis self.source_model = RedisKeyModel(redis) self.sort_filter_model = QSortFilterProxyModel() self.sort_filter_model.setFilterRole(KeyNameRole) self.sort_filter_model.setSourceModel(self.source_model) ui.tree.setModel(self.sort_filter_model) selection = ui.tree.selectionModel() selection.currentChanged.connect(self._on_current_changed) selection.selectionChanged.connect(self._on_selection_changed) # TODO: fix bug search of type "bl04:" still gives no result ui.filter_container.setVisible(False) add_menu = QMenu("Add") ui.add_string_action = add_menu.addAction("string") ui.add_list_action = add_menu.addAction("list") ui.add_set_action = add_menu.addAction("set") ui.add_hash_action = add_menu.addAction("hash") add_button = QToolButton() add_button.setMenu(add_menu) add_button.setPopupMode(QToolButton.InstantPopup) add_button.setIcon(QIcon.fromTheme("list-add")) ui.add_key_action = ui.db_toolbar.insertWidget(ui.remove_key_action, add_button) ui.add_string_action.triggered.connect( functools.partial(self._on_add_key, "string")) ui.add_list_action.triggered.connect( functools.partial(self._on_add_key, "list")) ui.add_set_action.triggered.connect( functools.partial(self._on_add_key, "set")) ui.add_hash_action.triggered.connect( functools.partial(self._on_add_key, "hash")) ui.update_db_action.triggered.connect(self._on_update_db) ui.flush_db_action.triggered.connect(self._on_flush_db) ui.remove_key_action.triggered.connect(self._on_remove_key) ui.touch_key_action.triggered.connect(self._on_touch_key) ui.persist_key_action.triggered.connect(self._on_persist_key) ui.copy_key_action.triggered.connect(self._on_copy_key) ui.filter_edit.textChanged.connect(self._on_filter_changed) def contextMenuEvent(self, event): pass def _get_selected_keys(self): selection = self.ui.tree.selectionModel() indexes = (self.sort_filter_model.mapToSource(i) for i in selection.selectedIndexes()) nodes = (self.source_model.data(i, NodeRole) for i in indexes) keys = tuple(node.key for node in nodes if node is not None and node.is_key()) return keys def _on_filter_changed(self, text): if not text.endswith("*"): text += "*" self.sort_filter_model.setFilterWildcard(text) def _on_current_changed(self, current, previous): current = self.sort_filter_model.mapToSource(current) node = self.source_model.data(current, Qt.UserRole) self.currentChanged.emit(node) def _on_selection_changed(self, selected, deselected): indexes = (self.sort_filter_model.mapToSource(i) for i in selected.indexes()) nodes = (self.source_model.data(i, NodeRole) for i in indexes) nodes = [node for node in nodes if node is not None] nodes_selected = bool(nodes) ui = self.ui ui.remove_key_action.setEnabled(nodes_selected) ui.touch_key_action.setEnabled(nodes_selected) ui.persist_key_action.setEnabled(nodes_selected) ui.copy_key_action.setEnabled(len(nodes) == 1) def _on_flush_db(self): result = QMessageBox.question( self, "Danger!", "This action will delete all data from the current database.\n" \ "Are you absolutely sure?") if result == QMessageBox.Yes: self.redis.flushdb() self.source_model.refresh() def _on_update_db(self): self.source_model.refresh() def _on_touch_key(self): keys = self._get_selected_keys() if keys: self.redis.touch(*keys) def _on_persist_key(self): keys = self._get_selected_keys() for key in keys: self.redis.persist(key) def _on_add_key(self, dtype): value = None if dtype == "string": value = "" elif dtype == "list": value = [] elif dtype == "set": value = set() elif dtype == "hash": value = {} item = Item(self.redis, "", dtype, -1, value) self.addKey.emit(item) def _on_remove_key(self): keys = self._get_selected_keys() if keys: self.redis.delete(*keys) self.ui.tree.clearSelection() def _on_copy_key(self): keys = self._get_selected_keys() assert len(keys) == 1 src = keys[0] dst, ok = QInputDialog.getText(self, f"Copy {src!r} to...", "New key") if ok: # redis.copy() only >= 6.2 #self.redis.copy(src, dst) self.redis[dst] = self.redis[src].value self.source_model.refresh()
class LicenseManagerDialog(DialogBase): """License Manager main dialog.""" CONTACT_LINK = 'https://support.continuum.io/' # TODO: Centralize this? # Url, Sender sig_url_clicked = Signal(object, object) def __init__(self, parent=None): """License Manager main dialog.""" super(LicenseManagerDialog, self).__init__(parent=parent) self.api = AnacondaAPI() # Widgets self.message_box = None # For testing self.button_add = ButtonPrimary('Add license') self.button_ok = ButtonNormal('Close') self.button_remove = ButtonNormal('Remove license') self.button_contact = ButtonLink('Please contact us.') self.label_info = LabelBase('Manage your Continuum Analytics ' 'license keys.') self.label_contact = LabelBase('Got a problem with your license? ') self.proxy_model = QSortFilterProxyModel(parent=self) self.model = LicenseModel(parent=self) self.table = LicenseTableView(parent=self) self.delegate = BackgroundDelegate(self.table) # Widget setup self.proxy_model.setSourceModel(self.model) self.table.setItemDelegate(self.delegate) self.table.setModel(self.proxy_model) self.setWindowTitle('License Manager') # Layouts layout_buttons = QHBoxLayout() layout_buttons.addWidget(self.label_info) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addStretch() layout_buttons.addWidget(self.button_add) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons_bottom = QHBoxLayout() layout_buttons_bottom.addWidget(self.label_contact) layout_buttons_bottom.addWidget(self.button_contact) layout_buttons_bottom.addStretch() layout_buttons_bottom.addWidget(self.button_ok) layout = QVBoxLayout() layout.addLayout(layout_buttons) layout.addWidget(SpacerVertical()) layout.addWidget(self.table) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons_bottom) self.setLayout(layout) # Signals self.button_add.clicked.connect(lambda: self.add_license()) self.button_remove.clicked.connect(self.remove_license) self.button_ok.clicked.connect(self.accept) self.button_contact.clicked.connect( lambda v=None: self.sig_url_clicked.emit(self.CONTACT_LINK, 'License Manager')) self.table.sig_dropped.connect(self.handle_drop) # Setup self.button_add.setFocus() self.load_licenses() def handle_drop(self, links): """Handle a drag and drop event.""" self.api.add_license(links) self.load_licenses() def _hide_columns(self): """Hide columns.""" for key, val in COL_MAP.items(): if val in HIDDEN_COLUMNS: self.table.setColumnHidden(key, True) def add_license(self, v=None, path=None): """Add license file.""" if path is None: filename, selected_filter = getopenfilename( self, 'Select license file', filters='License files (*.txt)', basedir=get_home_dir(), ) if filename: paths = [filename] else: paths = [] else: paths = [path] valid_licenses, invalid_licenses = self.api.add_license(paths) for path in invalid_licenses: text = ('File: <b>"{0}"</b>' '<br>is not a valid license file.').format(path) self.message_box = MessageBoxInformation( text=text, title="Invalid license file") self.message_box.exec_() if valid_licenses: self.load_licenses() def remove_license(self, row=None): """Remove license from file.""" if row is None: index = self.table.currentIndex() else: index = self.proxy_model.index(row, 0) model_index = self.proxy_model.mapToSource(index) row_data = self.model.row(model_index.row()) if row_data: text = ('Do you want to remove license for product:<br><br>' '<b>{product}</b> ({issued} - {end_date})') text = text.format(product=row_data.get('product'), end_date=row_data.get('end_date'), issued=row_data.get('issued')) self.message_box = MessageBoxRemove(title='Remove license', text=text) if self.message_box.exec_(): self.api.remove_license(row_data) self.load_licenses() def load_licenses(self): """Load license files.""" res = self.api.load_licenses() self.model.load_licenses(res) self.proxy_model.setSourceModel(self.model) self.table.resizeColumnsToContents() self._hide_columns() self.update_status() def count(self): """Return the number of items in the table.""" return self.table.model().rowCount() def update_status(self): """Update visible and enabled status for widgets based on actions.""" self.button_remove.setEnabled(bool(self.count()))