def drag_with_pixmap(list_widget: QListWidget) -> QDrag: """Create a QDrag object with a pixmap of the currently select list item. This method is useful when you have a QListWidget that displays custom widgets for each QListWidgetItem instance in the list (usually by calling ``QListWidget.setItemWidget(item, widget)``). When used in a ``QListWidget.startDrag`` method, this function creates a QDrag object that shows an image of the item being dragged (rather than an empty rectangle). Parameters ---------- list_widget : QListWidget The QListWidget for which to create a QDrag object. Returns ------- QDrag A QDrag instance with a pixmap of the currently selected item. Examples -------- >>> class QListWidget: ... def startDrag(self, supportedActions): ... drag = drag_with_pixmap(self) ... drag.exec_(supportedActions, Qt.MoveAction) """ drag = QDrag(list_widget) drag.setMimeData(list_widget.mimeData(list_widget.selectedItems())) size = list_widget.viewport().visibleRegion().boundingRect().size() pixmap = QPixmap(size) pixmap.fill(Qt.transparent) painter = QPainter(pixmap) for index in list_widget.selectedIndexes(): rect = list_widget.visualRect(index) painter.drawPixmap(rect, list_widget.viewport().grab(rect)) painter.end() drag.setPixmap(pixmap) drag.setHotSpot(list_widget.viewport().mapFromGlobal(QCursor.pos())) return drag
class RemoveLasersWindow(QWidget): def __init__(self, main_window): super().__init__() self.main_window = main_window lasers = self.main_window.lasers self.setWindowTitle("Remove laser") layout = QVBoxLayout() self.setLayout(layout) self.laser_list = QListWidget() self.laser_list.setSelectionMode(QAbstractItemView.ExtendedSelection) layout.addWidget(self.laser_list) names = [] for laser in lasers: settings = laser.get_settings() names.append(settings['name']) print(names) self.laser_list.addItems(names) self.removeButton = QPushButton("Remove") layout.addWidget(self.removeButton) self._createActions() self._connectActions() def _createActions(self): self.removeAction = QAction(self) self.removeAction.setText("Remove") def _connectActions(self): self.removeButton.clicked.connect(self.removeAction.trigger) self.removeAction.triggered.connect(self.remove_lasers) def remove_lasers(self): selected_rows = [x.row() for x in self.laser_list.selectedIndexes()] print(selected_rows) self.main_window.remove_lasers(selected_rows)
class RemoveRPWindow(QWidget): def __init__(self, main_window): super().__init__() self.main_window = main_window rps = self.main_window.rps self.setWindowTitle("Remove RedPitaya") layout = QVBoxLayout() self.setLayout(layout) self.laser_list = QListWidget() self.laser_list.setSelectionMode(QAbstractItemView.ExtendedSelection) layout.addWidget(self.laser_list) names = [] self.laser_list.addItems(rps) self.removeButton = QPushButton("Remove") layout.addWidget(self.removeButton) self._createActions() self._connectActions() def _createActions(self): self.removeAction = QAction(self) self.removeAction.setText("Remove") def _connectActions(self): self.removeButton.clicked.connect(self.removeAction.trigger) self.removeAction.triggered.connect(self.remove_lasers) def remove_lasers(self): selected_rows = [x.row() for x in self.laser_list.selectedIndexes()] print(selected_rows) self.main_window.remove_rps(selected_rows)
class HeaderEditDialog(QDialog): # name of the current preset; whether to set this preset as default; list of Columns header_changed = Signal(str, bool, list) def __init__(self, parent, table_header): super().__init__(parent) self.table_header = table_header self.default_preset_name = None self.preset_name = table_header.preset_name self.columns = deepcopy(table_header.columns) self.setupUi() self.update_output() def setupUi(self): self.resize(240, 400) self.vbox = QVBoxLayout(self) self.presetLabel = QLabel(self) self.columnList = QListWidget(self) self.setAsDefaultCheckbox = QCheckBox("Set as default preset", self) self.vbox.addWidget(self.presetLabel) self.vbox.addWidget(self.columnList) self.vbox.addWidget(self.setAsDefaultCheckbox) self.columnList.setDragDropMode(QListWidget.InternalMove) self.columnList.setDefaultDropAction(Qt.MoveAction) self.columnList.setSelectionMode(QListWidget.ExtendedSelection) self.columnList.setAlternatingRowColors(True) self.columnList.installEventFilter(self) self.columnList.setContextMenuPolicy(Qt.CustomContextMenu) self.columnList.customContextMenuRequested.connect(self.open_menu) self.columnList.model().rowsMoved.connect(self.read_columns_from_list) # for a dumb qss hack to make selected checkboxes not white on a light theme self.columnList.setObjectName("ColumnList") buttons = QDialogButtonBox.Reset | QDialogButtonBox.Save | QDialogButtonBox.Cancel self.buttonBox = QDialogButtonBox(buttons, self) self.vbox.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.resetButton = self.buttonBox.button(QDialogButtonBox.Reset) self.resetButton.clicked.connect(self.reset_to_stock) def eventFilter(self, object, event): if event.type() == QEvent.KeyPress: if event.key() == Qt.Key_Space or event.key() == Qt.Key_Return: self.toggle_selected_columns() return True if event.key() == Qt.Key_Delete: self.delete_selected() return True return False def update_output(self): self.presetLabel.setText("Preset: {}".format(self.preset_name)) self.setAsDefaultCheckbox.setChecked( CONFIG['default_header_preset'] == self.preset_name) self.columnList.clear() for column in self.columns: ColumnListItem(self.columnList, column) def accept(self): self.read_columns_from_list() self.header_changed.emit(self.preset_name, self.setAsDefaultCheckbox.isChecked(), self.columns) self.done(0) def reject(self): self.done(0) def reset_to_stock(self): self.columns = deepcopy(DEFAULT_COLUMNS) self.update_output() def read_columns_from_list(self): new_columns = [] for i in range(self.columnList.count()): item = self.columnList.item(i) new_columns.append(item.column) self.columns = new_columns def toggle_selected_columns(self): selected = self.columnList.selectedItems() for item in selected: value_now = item.data(Qt.CheckStateRole) item.setData(Qt.CheckStateRole, not value_now) self.columnList.reset( ) # @Improvement: is there a better way to update QListWidget? def open_menu(self, position): menu = QMenu(self) preset_menu = menu.addMenu('Presets') preset_menu.addAction('New preset', self.new_preset_dialog) preset_menu.addSeparator() preset_names = CONFIG.get_header_presets() if len(preset_names) == 0: action = preset_menu.addAction('No presets') action.setEnabled(False) else: delete_menu = menu.addMenu('Delete preset') for name in preset_names: preset_menu.addAction(name, partial(self.load_preset, name)) delete_menu.addAction(name, partial(self.delete_preset, name)) menu.addSeparator() menu.addAction('New column...', self.create_new_column_dialog) if len(self.columnList.selectedIndexes()) > 0: menu.addAction('Delete selected', self.delete_selected) menu.popup(self.columnList.viewport().mapToGlobal(position)) def load_preset(self, name): new_columns = CONFIG.load_header_preset(name) if not new_columns: return self.columns = new_columns self.preset_name = name self.update_output() def new_preset_dialog(self): d = QInputDialog(self) d.setLabelText('Enter the new name for the new preset:') d.setWindowTitle('Create new preset') d.textValueSelected.connect(self.create_new_preset) d.open() def create_new_preset(self, name): if name in CONFIG.get_header_presets(): show_warning_dialog( self, "Preset creation error", 'Preset named "{}" already exists.'.format(name)) return if len(name.strip()) == 0: show_warning_dialog( self, "Preset creation error", 'This preset name is not allowed.'.format(name)) return self.preset_name = name self.update_output() CONFIG.save_header_preset(name, self.columns) def delete_preset(self, name): CONFIG.delete_header_preset(name) if name == self.preset_name: self.columns = deepcopy(DEFAULT_COLUMNS) self.update_output() def create_new_column_dialog(self): d = CreateNewColumnDialog(self) d.add_new_column.connect(self.add_new_column) d.setWindowTitle('Create new column') d.open() def add_new_column(self, name, title): new_column = Column(name, title) # if the last column is message, insert this column before it (I think that makes sense?) if len(self.columns) == 0: self.columns.append(new_column) elif self.columns[-1].name in ('message', 'msg'): self.columns.insert(-1, new_column) else: self.columns.append(new_column) self.update_output() def delete_selected(self): selected = self.columnList.selectedItems() for item in selected: self.columnList.takeItem(self.columnList.row(item)) self.read_columns_from_list() self.update_output()
class RemoteKernelSetupDialog(QDialog): """Dialog to connect to existing kernels (either local or remote).""" def __init__(self, parent=None): super(RemoteKernelSetupDialog, self).__init__(parent) self.setWindowTitle(_('Setup remote kernel')) self.TEXT_FETCH_REMOTE_CONN_FILES_BTN = 'Fetch remote connection files' self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME = 'jupyter --runtime-dir' # Name of the connection cfg_name_label = QLabel(_('Configuration name:')) self.cfg_name_line_edit = QLineEdit() # SSH connection hostname_label = QLabel(_('Hostname:')) self.hostname_lineedit = QLineEdit() port_label = QLabel(_('Port:')) self.port_lineeidt = QLineEdit() self.port_lineeidt.setMaximumWidth(75) username_label = QLabel(_('Username:'******'Password:'******'SSH keyfile:')) self.pw = QLineEdit() self.pw.setEchoMode(QLineEdit.Password) self.pw_radio.toggled.connect(self.pw.setEnabled) self.keyfile_radio.toggled.connect(self.pw.setDisabled) self.keyfile_path_lineedit = QLineEdit() keyfile_browse_btn = QPushButton(_('Browse')) keyfile_browse_btn.clicked.connect(self.select_ssh_key) keyfile_layout = QHBoxLayout() keyfile_layout.addWidget(self.keyfile_path_lineedit) keyfile_layout.addWidget(keyfile_browse_btn) passphrase_label = QLabel(_('Passphrase:')) self.passphrase_lineedit = QLineEdit() self.passphrase_lineedit.setPlaceholderText(_('Optional')) self.passphrase_lineedit.setEchoMode(QLineEdit.Password) self.keyfile_radio.toggled.connect(self.keyfile_path_lineedit.setEnabled) self.keyfile_radio.toggled.connect(self.passphrase_lineedit.setEnabled) self.keyfile_radio.toggled.connect(keyfile_browse_btn.setEnabled) self.keyfile_radio.toggled.connect(passphrase_label.setEnabled) self.pw_radio.toggled.connect(self.keyfile_path_lineedit.setDisabled) self.pw_radio.toggled.connect(self.passphrase_lineedit.setDisabled) self.pw_radio.toggled.connect(keyfile_browse_btn.setDisabled) self.pw_radio.toggled.connect(passphrase_label.setDisabled) # Button to fetch JSON files listing # self.kf_fetch_conn_files_btn = QPushButton(_(self.TEXT_FETCH_REMOTE_CONN_FILES_BTN)) # self.kf_fetch_conn_files_btn.clicked.connect(self.fill_combobox_with_fetched_remote_connection_files) # self.cb_remote_conn_files = QComboBox() # self.cb_remote_conn_files.currentIndexChanged.connect(self._take_over_selected_remote_configuration_file) # Remote kernel groupbox self.start_remote_kernel_group = QGroupBox(_("Start remote kernel")) # Advanced settings to get remote connection files jupyter_runtime_location_cmd_label = QLabel(_('Command to get Jupyter runtime:')) self.jupyter_runtime_location_cmd_lineedit = QLineEdit() self.jupyter_runtime_location_cmd_lineedit.setPlaceholderText(_(self.DEFAULT_CMD_FOR_JUPYTER_RUNTIME)) # SSH layout ssh_layout = QGridLayout() ssh_layout.addWidget(cfg_name_label, 0, 0) ssh_layout.addWidget(self.cfg_name_line_edit, 0, 2) ssh_layout.addWidget(hostname_label, 1, 0, 1, 2) ssh_layout.addWidget(self.hostname_lineedit, 1, 2) ssh_layout.addWidget(port_label, 1, 3) ssh_layout.addWidget(self.port_lineeidt, 1, 4) ssh_layout.addWidget(username_label, 2, 0, 1, 2) ssh_layout.addWidget(self.username_lineedit, 2, 2, 1, 3) # SSH authentication layout auth_layout = QGridLayout() auth_layout.addWidget(self.pw_radio, 1, 0) auth_layout.addWidget(pw_label, 1, 1) auth_layout.addWidget(self.pw, 1, 2) auth_layout.addWidget(self.keyfile_radio, 2, 0) auth_layout.addWidget(keyfile_label, 2, 1) auth_layout.addLayout(keyfile_layout, 2, 2) auth_layout.addWidget(passphrase_label, 3, 1) auth_layout.addWidget(self.passphrase_lineedit, 3, 2) auth_layout.addWidget(jupyter_runtime_location_cmd_label, 4, 1) auth_layout.addWidget(self.jupyter_runtime_location_cmd_lineedit, 4, 2) # auth_layout.addWidget(self.kf_fetch_conn_files_btn, 5, 1) # auth_layout.addWidget(self.cb_remote_conn_files, 5, 2) auth_group.setLayout(auth_layout) # Remote kernel layout self.rm_group = QGroupBox(_("Setup up of a remote connection")) self.rm_group.setEnabled(False) rm_layout = QVBoxLayout() rm_layout.addLayout(ssh_layout) rm_layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) rm_layout.addWidget(auth_group) self.rm_group.setLayout(rm_layout) self.rm_group.setCheckable(False) # Ok and Cancel buttons self.accept_btns = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.accept_btns.accepted.connect(self.accept) self.accept_btns.rejected.connect(self.reject) btns_layout = QHBoxLayout() btns_layout.addWidget(self.accept_btns) # Dialog layout layout = QVBoxLayout() layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 8))) # layout.addSpacerItem(QSpacerItem(QSpacerItem(0, 12))) layout.addWidget(self.rm_group) layout.addLayout(btns_layout) # Main layout hbox_layout = QHBoxLayout(self) # Left side with the list of all remote connection configurations items_label = QLabel(text="Configured remote locations") self.items_list = QListWidget() self.items_list.clicked.connect(self._on_items_list_click) items_layout = QVBoxLayout() items_layout.addWidget(items_label) items_layout.addWidget(self.items_list) edit_delete_new_buttons_layout = QHBoxLayout() edit_btn = QPushButton(text="Edit") add_btn = QPushButton(text="Add") delete_btn = QPushButton(text="Delete") add_btn.clicked.connect(self._on_add_btn_click) edit_btn.clicked.connect(self._on_edit_btn_click) delete_btn.clicked.connect(self._on_delete_btn_click) edit_delete_new_buttons_layout.addWidget(add_btn) edit_delete_new_buttons_layout.addWidget(edit_btn) edit_delete_new_buttons_layout.addWidget(delete_btn) items_layout.addLayout(edit_delete_new_buttons_layout) hbox_layout.addSpacerItem(QSpacerItem(10, 0)) hbox_layout.addLayout(items_layout) hbox_layout.addLayout(layout) self.lst_with_connecion_configs = [] def _on_items_list_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _clear_remote_connection_input_fields(self): self.keyfile_path_lineedit.setText("") self.passphrase_lineedit.setText("") self.hostname_lineedit.setText("") self.username_lineedit.setText("") self.port_lineeidt.setText("") self.cfg_name_line_edit.setText("") self.jupyter_runtime_location_cmd_lineedit.setText("") self.keyfile_radio.setChecked(False) self.pw_radio.setChecked(False) def _update_remote_connection_input_fields(self, remote_conn_settings): self.keyfile_path_lineedit.setText(remote_conn_settings.keyfile_path) self.passphrase_lineedit.setText(remote_conn_settings.password) self.hostname_lineedit.setText(remote_conn_settings.hostname) self.username_lineedit.setText(remote_conn_settings.username) self.port_lineeidt.setText(str(remote_conn_settings.port)) self.cfg_name_line_edit.setText(remote_conn_settings.connection_name) self.jupyter_runtime_location_cmd_lineedit.setText(remote_conn_settings.cmd_for_jupyter_runtime_location) self.keyfile_radio.setChecked(remote_conn_settings.keyfile_path is not None) self.pw_radio.setChecked(remote_conn_settings.password is not None) def _on_add_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings username = self.username_lineedit.text() passphrase = self.passphrase_lineedit.text() hostname = self.hostname_lineedit.text() keyfile_path = self.keyfile_path_lineedit.text() port = int(self.port_lineeidt.text()) if self.port_lineeidt.text() != "" else 22 jup_runtime_cmd = self.jupyter_runtime_location_cmd_lineedit.text() cfg_name = self.cfg_name_line_edit.text() cfg = RemoteConnectionSettings( username=username, hostname=hostname, keyfile_path=keyfile_path, port=port, connection_name=cfg_name, cmd_for_jupyter_runtime_location=jup_runtime_cmd, password=passphrase ) self.lst_with_connecion_configs.append(cfg) self._update_list_with_configs() self.rm_group.setEnabled(False) def _on_edit_btn_click(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings self.rm_group.setEnabled(True) idx_of_config = self.items_list.selectedIndexes()[0].row() cfg = self.lst_with_connecion_configs[idx_of_config] if isinstance(cfg, RemoteConnectionSettings): self._update_remote_connection_input_fields(cfg) else: show_info_dialog("Information", "This functionality is still not available") def _on_delete_btn_click(self): idx_of_config = self.items_list.selectedIndexes()[0].row() self.lst_with_connecion_configs.pop(idx_of_config) self._update_list_with_configs() def select_ssh_key(self): kf = getopenfilename(self, _('Select SSH keyfile'), get_home_dir(), '*.pem;;*')[0] self.keyfile_path_lineedit.setText(kf) def _take_over_selected_remote_configuration_file(self, chosen_idx_of_combobox_with_remote_conn_files): remote_path_filename = self.remote_conn_file_paths[chosen_idx_of_combobox_with_remote_conn_files] self.cf.setText(remote_path_filename) def set_connection_configs(self, lst_with_connecion_configs): self.lst_with_connecion_configs = lst_with_connecion_configs self._update_list_with_configs() def _update_list_with_configs(self): from .kernelconnectmaindialog import LocalConnectionSettings, RemoteConnectionSettings # now, fill the list self.items_list.clear() for cfg in self.lst_with_connecion_configs: if isinstance(cfg, LocalConnectionSettings): self.items_list.addItem(f"Local: {cfg.connection_name}") elif isinstance(cfg, RemoteConnectionSettings): self.items_list.addItem(f"Remote: {cfg.connection_name}") def get_connection_settings(self): return self.lst_with_connecion_configs