class AcceptFiles(QDialog): def __init__(self, files): super().__init__() self.ok = QPushButton("Add", self) self.ok.clicked.connect(self.accept) discard = QPushButton("Discard", self) discard.clicked.connect(self.close) self.files = QListWidget(self) self.files.setSelectionMode(QAbstractItemView.ExtendedSelection) for file_name in files: self.files.addItem(file_name) for i in range(self.files.count()): self.files.item(i).setSelected(True) self.ok.setDefault(True) self.ok.setAutoDefault(True) layout = QVBoxLayout() layout.addWidget(QLabel("Found {} files".format(len(files)))) layout.addWidget(self.files) butt_layout = QHBoxLayout() butt_layout.addWidget(discard) butt_layout.addStretch() butt_layout.addWidget(self.ok) layout.addLayout(butt_layout) self.setLayout(layout) def selection_changed(self): if self.files.selectedItems().count() == 0: self.ok.setDisabled(True) else: self.ok.setEnabled(True) def get_files(self): return [str(item.text()) for item in self.files.selectedItems()]
class DuplicateNormConfig(SiriusDialog): """Auxiliary window to duplicate a normalized config.""" insertConfig = Signal(float, str, dict) def __init__(self, parent, psname2strength): """Initialize object.""" super().__init__(parent) self.setObjectName('BOApp') self.setWindowTitle('Duplicate Normalized Configuration') self.psname2strength = psname2strength self._setupUi() def _setupUi(self): self.le_label = QLineEdit(self) self.sb_time = QDoubleSpinBoxPlus(self) self.sb_time.setMaximum(490) self.sb_time.setDecimals(3) self.bt_duplic = QPushButton('Duplicate', self) self.bt_duplic.setAutoDefault(False) self.bt_duplic.setDefault(False) self.bt_duplic.clicked.connect(self._emitConfigData) self.bt_cancel = QPushButton('Cancel', self) self.bt_cancel.setAutoDefault(False) self.bt_cancel.setDefault(False) self.bt_cancel.clicked.connect(self.close) # layout lay = QGridLayout() lay.setVerticalSpacing(15) lay.addWidget(QLabel('<h4>Duplicate Normalized Configuration</h4>', self), 0, 0, 1, 2, alignment=Qt.AlignCenter) lay.addWidget( QLabel( 'Choose a label and a time to insert\n' 'the new configuration:', self), 1, 0, 1, 2) lay.addWidget(QLabel('Label: ', self), 2, 0) lay.addWidget(self.le_label, 2, 1) lay.addWidget(QLabel('Time [ms]: ', self), 3, 0) lay.addWidget(self.sb_time, 3, 1) lay.addWidget(self.bt_cancel, 4, 0) lay.addWidget(self.bt_duplic, 4, 1) self.setLayout(lay) def _emitConfigData(self): time = self.sb_time.value() label = self.le_label.text() psname2strength = self.psname2strength self.insertConfig.emit(time, label, psname2strength) self.close()
def __init__(self, parent): super(KiteIntegrationInfo, self).__init__(parent) # Images images_layout = QHBoxLayout() if is_dark_interface(): icon_filename = 'spyder_kite.svg' else: icon_filename = 'spyder_kite_dark.svg' image_path = get_image_path(icon_filename) image = QPixmap(image_path) image_label = QLabel() screen = QApplication.primaryScreen() device_image_ratio = screen.devicePixelRatio() if device_image_ratio > 1: image.setDevicePixelRatio(device_image_ratio) else: image_height = image.height() * 0.5 image_width = image.width() * 0.5 image = image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image_label.setPixmap(image) images_layout.addStretch() images_layout.addWidget(image_label) images_layout.addStretch() # Label integration_label = QLabel( _("Now Spyder can use <a href=\"{kite_url}\">Kite</a> to " "provide better and more accurate code completions in its " "editor <br>for the most important packages in the Python " "scientific ecosystem, such as Numpy, <br>Matplotlib and " "Pandas.<br><br>Would you like to install it or learn more " "about it?<br><br><i>Note:</i> Kite is free to use " "but is not an open source program.").format( kite_url=KITE_SPYDER_URL)) integration_label.setOpenExternalLinks(True) # Buttons buttons_layout = QHBoxLayout() learn_more_button = QPushButton(_('Learn more')) learn_more_button.setAutoDefault(False) install_button = QPushButton(_('Install Kite')) install_button.setAutoDefault(False) dismiss_button = QPushButton(_('Dismiss')) dismiss_button.setAutoDefault(False) buttons_layout.addStretch() buttons_layout.addWidget(install_button) buttons_layout.addWidget(learn_more_button) buttons_layout.addWidget(dismiss_button) general_layout = QVBoxLayout() general_layout.addLayout(images_layout) general_layout.addWidget(integration_label) general_layout.addLayout(buttons_layout) self.setLayout(general_layout) learn_more_button.clicked.connect(self.sig_learn_more_button_clicked) install_button.clicked.connect(self.sig_install_button_clicked) dismiss_button.clicked.connect(self.sig_dismiss_button_clicked)
def __init__(self, parent=None): """ """ super(WriteQuitWidget, self).__init__(parent) self.parent = parent # Generate Buttons wbtn = QPushButton(self) wbtn.setText('Write') wbtn.setAutoDefault(False) wbtn.clicked.connect(self.parent.write_out) wqbtn = QPushButton(self) wqbtn.setText('Write\n Quit') wqbtn.setAutoDefault(False) wqbtn.clicked.connect(self.parent.write_quit) qbtn = QPushButton(self) qbtn.setText('Quit') qbtn.setAutoDefault(False) qbtn.clicked.connect(self.parent.quit) # Layout hbox = QHBoxLayout() hbox.addWidget(wbtn) hbox.addWidget(wqbtn) hbox.addWidget(qbtn) self.setLayout(hbox)
class TextEditor(QDialog): """Array Editor Dialog""" def __init__(self, text, title='', font=None, parent=None, readonly=False, size=(400, 300)): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.text = None self.btn_save_and_close = None # Display text as unicode if it comes as bytes, so users see # its right representation if is_binary_string(text): self.is_binary = True text = to_text_string(text, 'utf8') else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.setReadOnly(readonly) self.edit.textChanged.connect(self.text_changed) self.edit.setPlainText(text) if font is None: font = get_font() self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(ima.icon('edit')) self.setWindowTitle(_("Text editor") + \ "%s" % (" - "+str(title) if str(title) else "")) self.resize(size[0], size[1]) @Slot() def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = to_binary_string(self.edit.toPlainText(), 'utf8') else: self.text = to_text_string(self.edit.toPlainText()) if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" try: to_text_string(value, 'utf8') return True except: return False
def __init__(self, parent): super(KiteIntegrationInfo, self).__init__(parent) # Images images_layout = QHBoxLayout() icon_filename = 'kite_completions' image_path = get_image_path(icon_filename) image = QPixmap(image_path) image_label = QLabel() image_label = QLabel() image_height = int(image.height() * DialogStyle.IconScaleFactor) image_width = int(image.width() * DialogStyle.IconScaleFactor) image = image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image_label.setPixmap(image) images_layout.addStretch() images_layout.addWidget(image_label) images_layout.addStretch() ilayout = QHBoxLayout() ilayout.addLayout(images_layout) # Label integration_label_title = QLabel( "Get better code completions in Spyder") integration_label_title.setStyleSheet( f"font-size: {DialogStyle.TitleFontSize}") integration_label_title.setWordWrap(True) integration_label = QLabel( _("Now Spyder can use Kite to provide better code " "completions for key packages in the scientific Python " "Ecosystem. Install Kite for a better editor experience in " "Spyder. <br><br>Kite is free to use but is not open " "source. <a href=\"{kite_url}\">Learn more about Kite </a>"). format(kite_url=KITE_SPYDER_URL)) integration_label.setStyleSheet( f"font-size: {DialogStyle.ContentFontSize}") integration_label.setOpenExternalLinks(True) integration_label.setWordWrap(True) integration_label.setFixedWidth(360) label_layout = QVBoxLayout() label_layout.addWidget(integration_label_title) label_layout.addWidget(integration_label) # Buttons install_button_color = QStylePalette.COLOR_ACCENT_2 install_button_hover = QStylePalette.COLOR_ACCENT_3 install_button_pressed = QStylePalette.COLOR_ACCENT_4 dismiss_button_color = QStylePalette.COLOR_BACKGROUND_4 dismiss_button_hover = QStylePalette.COLOR_BACKGROUND_5 dismiss_button_pressed = QStylePalette.COLOR_BACKGROUND_6 font_color = QStylePalette.COLOR_TEXT_1 buttons_layout = QHBoxLayout() install_button = QPushButton(_('Install Kite')) install_button.setAutoDefault(False) install_button.setStyleSheet( ("QPushButton {{ " "background-color: {background_color};" "border-color: {border_color};" "font-size: {font_size};" "color: {font_color};" "padding: {padding}}}" "QPushButton:hover:!pressed {{ " "background-color: {color_hover}}}" "QPushButton:pressed {{ " "background-color: {color_pressed}}}").format( background_color=install_button_color, border_color=install_button_color, font_size=DialogStyle.ButtonsFontSize, font_color=font_color, padding=DialogStyle.ButtonsPadding, color_hover=install_button_hover, color_pressed=install_button_pressed)) dismiss_button = QPushButton(_('Dismiss')) dismiss_button.setAutoDefault(False) dismiss_button.setStyleSheet( ("QPushButton {{ " "background-color: {background_color};" "border-color: {border_color};" "font-size: {font_size};" "color: {font_color};" "padding: {padding}}}" "QPushButton:hover:!pressed {{ " "background-color: {color_hover}}}" "QPushButton:pressed {{ " "background-color: {color_pressed}}}").format( background_color=dismiss_button_color, border_color=dismiss_button_color, font_size=DialogStyle.ButtonsFontSize, font_color=font_color, padding=DialogStyle.ButtonsPadding, color_hover=dismiss_button_hover, color_pressed=dismiss_button_pressed)) buttons_layout.addStretch() buttons_layout.addWidget(install_button) if not MAC: buttons_layout.addSpacing(10) buttons_layout.addWidget(dismiss_button) # Buttons with label vertical_layout = QVBoxLayout() if not MAC: vertical_layout.addStretch() vertical_layout.addLayout(label_layout) vertical_layout.addSpacing(20) vertical_layout.addLayout(buttons_layout) vertical_layout.addStretch() else: vertical_layout.addLayout(label_layout) vertical_layout.addLayout(buttons_layout) general_layout = QHBoxLayout() general_layout.addStretch() general_layout.addLayout(ilayout) general_layout.addSpacing(15) general_layout.addLayout(vertical_layout) general_layout.addStretch() self.setLayout(general_layout) # Signals install_button.clicked.connect(self.sig_install_button_clicked) dismiss_button.clicked.connect(self.sig_dismiss_button_clicked) self.setStyleSheet( f"background-color: {QStylePalette.COLOR_BACKGROUND_2}") self.setContentsMargins(18, 40, 18, 40) if not MAC: self.setFixedSize(800, 350)
class CondaPackagesWidget(QWidget): """ Conda Packages Widget. """ # Location of updated repo.json files from continuum/binstar CONDA_CONF_PATH = get_conf_path('repo') # Location of continuum/anaconda default repos shipped with conda-manager DATA_PATH = get_module_data_path() # file inside DATA_PATH with metadata for conda packages DATABASE_FILE = 'packages.ini' sig_worker_ready = Signal() sig_packages_ready = Signal() sig_environment_created = Signal(object, object) sig_environment_removed = Signal(object, object) sig_environment_cloned = Signal(object, object) sig_channels_updated = Signal(tuple, tuple) # channels, active_channels sig_process_cancelled = Signal() sig_next_focus = Signal() sig_packages_busy = Signal() def __init__(self, parent, name=None, prefix=None, channels=(), active_channels=(), conda_url='https://conda.anaconda.org', conda_api_url='https://api.anaconda.org', setup=True, data_directory=None, extra_metadata={}): super(CondaPackagesWidget, self).__init__(parent) # Check arguments: active channels, must be witbhin channels for ch in active_channels: if ch not in channels: raise Exception("'active_channels' must be also within " "'channels'") if data_directory is None: data_directory = self.CONDA_CONF_PATH self._parent = parent self._current_action_name = '' self._hide_widgets = False self._metadata = extra_metadata # From repo.continuum self._metadata_links = {} # Bundled metadata self.api = ManagerAPI() self.busy = False self.data_directory = data_directory self.conda_url = conda_url self.conda_api_url = conda_api_url self.name = name self.package_blacklist = [] self.prefix = prefix self.root_prefix = self.api.ROOT_PREFIX self.style_sheet = None self.message = '' self.apply_actions_dialog = None self.conda_errors = [] self.message_box_error = None self.token = None if channels: self._channels = channels self._active_channels = active_channels else: self._channels = self.api.conda_get_condarc_channels() self._active_channels = self._channels[:] try: import spyderlib.utils.icon_manager as ima icon_options = ima.icon('tooloptions') except Exception: import qtawesome as qta icon_options = qta.icon('fa.cog') # Widgets self.cancel_dialog = ClosePackageManagerDialog self.bbox = QDialogButtonBox(Qt.Horizontal) self.button_cancel = QPushButton('Cancel') self.button_channels = QPushButton(_('Channels')) self.button_ok = QPushButton(_('Ok')) self.button_update = QPushButton(_('Update index...')) self.button_apply = QPushButton(_('Apply')) self.button_clear = QPushButton(_('Clear')) self.button_options = QToolButton() self.combobox_filter = DropdownPackageFilter(self) self.frame_top = FramePackageTop() self.frame_bottom = FramePackageTop() self.progress_bar = ProgressBarPackage(self) self.status_bar = LabelPackageStatus(self) self.table = TableCondaPackages(self) self.textbox_search = LineEditSearch(self) self.widgets = [self.button_update, self.button_channels, self.combobox_filter, self.textbox_search, self.table, self.button_ok, self.button_apply, self.button_clear, self.button_options] self.table_first_row = FirstRowWidget( widget_before=self.textbox_search) self.table_last_row = LastRowWidget( widgets_after=[self.button_apply, self.button_clear, self.button_cancel, self.combobox_filter]) # Widget setup self.button_options.setPopupMode(QToolButton.InstantPopup) self.button_options.setIcon(icon_options) self.button_options.setAutoRaise(True) max_height = self.status_bar.fontMetrics().height() max_width = self.textbox_search.fontMetrics().width('M'*23) self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole) self.button_ok.setAutoDefault(True) self.button_ok.setDefault(True) self.button_ok.setMaximumSize(QSize(0, 0)) self.button_ok.setVisible(False) self.combobox_filter.addItems([k for k in C.COMBOBOX_VALUES_ORDERED]) self.combobox_filter.setMinimumWidth(120) self.progress_bar.setMaximumHeight(max_height*1.2) self.progress_bar.setMaximumWidth(max_height*12) self.progress_bar.setTextVisible(False) self.progress_bar.setVisible(False) self.setMinimumSize(QSize(480, 300)) self.setWindowTitle(_("Conda Package Manager")) self.status_bar.setFixedHeight(max_height*1.5) self.textbox_search.setMaximumWidth(max_width) self.textbox_search.setPlaceholderText('Search Packages') self.table_first_row.setMaximumHeight(0) self.table_last_row.setMaximumHeight(0) self.table_last_row.setVisible(False) self.table_first_row.setVisible(False) # Layout top_layout = QHBoxLayout() top_layout.addWidget(self.combobox_filter) top_layout.addWidget(self.button_channels) top_layout.addWidget(self.button_update) top_layout.addWidget(self.textbox_search) top_layout.addStretch() top_layout.addWidget(self.button_options) middle_layout = QVBoxLayout() middle_layout.addWidget(self.table_first_row) middle_layout.addWidget(self.table) middle_layout.addWidget(self.table_last_row) bottom_layout = QHBoxLayout() bottom_layout.addWidget(self.status_bar) bottom_layout.addStretch() bottom_layout.addWidget(self.progress_bar) bottom_layout.addWidget(self.button_cancel) bottom_layout.addWidget(self.button_apply) bottom_layout.addWidget(self.button_clear) layout = QVBoxLayout(self) layout.addLayout(top_layout) layout.addLayout(middle_layout) layout.addLayout(bottom_layout) layout.addSpacing(6) self.setLayout(layout) self.setTabOrder(self.combobox_filter, self.button_channels) self.setTabOrder(self.button_channels, self.button_update) self.setTabOrder(self.button_update, self.textbox_search) self.setTabOrder(self.textbox_search, self.table_first_row) self.setTabOrder(self.table, self.table_last_row) self.setTabOrder(self.table_last_row, self.button_apply) self.setTabOrder(self.button_apply, self.button_clear) self.setTabOrder(self.button_clear, self.button_cancel) # Signals and slots self.api.sig_repodata_updated.connect(self._repodata_updated) self.combobox_filter.currentIndexChanged.connect(self.filter_package) self.button_apply.clicked.connect(self.apply_multiple_actions) self.button_clear.clicked.connect(self.clear_actions) self.button_cancel.clicked.connect(self.cancel_process) self.button_channels.clicked.connect(self.show_channels_dialog) self.button_update.clicked.connect(self.update_package_index) self.textbox_search.textChanged.connect(self.search_package) self.table.sig_conda_action_requested.connect(self._run_conda_action) self.table.sig_actions_updated.connect(self.update_actions) self.table.sig_pip_action_requested.connect(self._run_pip_action) self.table.sig_status_updated.connect(self.update_status) self.table.sig_next_focus.connect(self.table_last_row.handle_tab) self.table.sig_previous_focus.connect( lambda: self.table_first_row.widget_before.setFocus()) self.table_first_row.sig_enter_first.connect(self._handle_tab_focus) self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus) # Setup self.api.client_set_domain(conda_api_url) self.api.set_data_directory(self.data_directory) self._load_bundled_metadata() self.update_actions(0) if setup: self.set_environment(name=name, prefix=prefix) self.setup() # --- Helpers # ------------------------------------------------------------------------- def _handle_tab_focus(self): self.table.setFocus() if self.table.proxy_model: index = self.table.proxy_model.index(0, 0) self.table.setCurrentIndex(index) def _handle_backtab_focus(self): self.table.setFocus() if self.table.proxy_model: row = self.table.proxy_model.rowCount() - 1 index = self.table.proxy_model.index(row, 0) self.table.setCurrentIndex(index) # --- Callbacks # ------------------------------------------------------------------------- def _load_bundled_metadata(self): """ """ logger.debug('') parser = cp.ConfigParser() db_file = CondaPackagesWidget.DATABASE_FILE with open(osp.join(self.DATA_PATH, db_file)) as f: parser.readfp(f) for name in parser.sections(): metadata = {} for key, data in parser.items(name): metadata[key] = data self._metadata_links[name] = metadata def _setup_packages(self, worker, data, error): """ """ if error: logger.error(error) else: logger.debug('') combobox_index = self.combobox_filter.currentIndex() status = C.PACKAGE_STATUS[combobox_index] packages = worker.packages # Remove blacklisted packages for package in self.package_blacklist: if package in packages: packages.pop(package) for i, row in enumerate(data): if package == data[i][C.COL_NAME]: data.pop(i) self.table.setup_model(packages, data, self._metadata_links) self.combobox_filter.setCurrentIndex(combobox_index) self.filter_package(status) if self._current_model_index: self.table.setCurrentIndex(self._current_model_index) self.table.verticalScrollBar().setValue(self._current_table_scroll) if error: self.update_status(error, False) self.sig_packages_ready.emit() self.table.setFocus() def get_logged_user_list_channels(self): channels = [] for ch in self._active_channels: if self.conda_url in ch and 'repo.continuum' not in ch: channel = ch.split('/')[-1] channels.append(channel) return channels def _prepare_model_data(self, worker=None, output=None, error=None): """ """ if error: logger.error(error) else: logger.debug('') packages, apps = output # worker = self.api.pip_list(prefix=self.prefix) # worker.sig_finished.connect(self._pip_list_ready) logins = self.get_logged_user_list_channels() worker = self.api.client_multi_packages(logins=logins, access='private') worker.sig_finished.connect(self._user_private_packages_ready) worker.packages = packages worker.apps = apps def _user_private_packages_ready(self, worker, output, error): if error: logger.error(error) else: logger.debug('') packages = worker.packages apps = worker.apps worker = self.api.pip_list(prefix=self.prefix) worker.sig_finished.connect(self._pip_list_ready) worker.packages = packages worker.apps = apps # private_packages = {} # if output: # all_private_packages = output # for item in all_private_packages: # name = item.get('name', '') # public = item.get('public', True) # package_types = item.get('package_types', []) # latest_version = item.get('latest_version', '') # if name and not public and 'conda' in package_types: # private_packages[name] = {'versions': item.get('versions', []), # 'app_entry': {}, # 'type': {}, # 'size': {}, # 'latest_version': latest_version, # } worker.private_packages = output def _pip_list_ready(self, worker, pip_packages, error): """ """ if error: logger.error(error) else: logger.debug('') packages = worker.packages private_packages = worker.private_packages linked_packages = self.api.conda_linked(prefix=self.prefix) data = self.api.client_prepare_packages_data(packages, linked_packages, pip_packages, private_packages) combobox_index = self.combobox_filter.currentIndex() status = C.PACKAGE_STATUS[combobox_index] # Remove blacklisted packages for package in self.package_blacklist: if package in packages: packages.pop(package) for i, row in enumerate(data): if package == data[i][C.COL_NAME]: data.pop(i) self.table.setup_model(packages, data, self._metadata_links) self.combobox_filter.setCurrentIndex(combobox_index) self.filter_package(status) if self._current_model_index: self.table.setCurrentIndex(self._current_model_index) self.table.verticalScrollBar().setValue(self._current_table_scroll) if error: self.update_status(str(error), False) self.sig_packages_ready.emit() self.table.setFocus() def _repodata_updated(self, paths): """ """ worker = self.api.client_load_repodata(paths, extra_data={}, metadata=self._metadata) worker.paths = paths worker.sig_finished.connect(self._prepare_model_data) def _metadata_updated(self, worker, path, error): """ """ if error: logger.error(error) else: logger.debug('') if path and osp.isfile(path): with open(path, 'r') as f: data = f.read() try: self._metadata = json.loads(data) except Exception: self._metadata = {} else: self._metadata = {} self.api.update_repodata(self._channels) # --- # ------------------------------------------------------------------------- def _run_multiple_actions(self, worker=None, output=None, error=None): """ """ logger.error(str(error)) if output and isinstance(output, dict): conda_error_type = output.get('error_type', None) conda_error = output.get('error', None) if conda_error_type or conda_error: self.conda_errors.append((conda_error_type, conda_error)) logger.error((conda_error_type, conda_error)) if self._multiple_process: status, func = self._multiple_process.popleft() self.update_status(status) worker = func() worker.sig_finished.connect(self._run_multiple_actions) worker.sig_partial.connect(self._partial_output_ready) else: if self.conda_errors and self.message_box_error: text = "The following errors occured:" error = '' for conda_error in self.conda_errors: error += str(conda_error[0]) + ':\n' error += str(conda_error[1]) + '\n\n' dlg = self.message_box_error(text=text, error=error, title='Conda process error') dlg.setMinimumWidth(400) dlg.exec_() self.update_status('', hide=False) self.setup() def _pip_process_ready(self, worker, output, error): """ """ if error is not None: status = _('there was an error') self.update_status(hide=False, message=status) else: self.update_status(hide=True) self.setup() def _conda_process_ready(self, worker, output, error): """ """ if error is not None: status = _('there was an error') self.update_status(hide=False, message=status) else: self.update_status(hide=True) conda_error = None conda_error_type = None if output and isinstance(output, dict): conda_error_type = output.get('error_type') conda_error = output.get('error') if conda_error_type or conda_error: logger.error((conda_error_type, conda_error)) dic = self._temporal_action_dic if dic['action'] == C.ACTION_CREATE: self.sig_environment_created.emit(conda_error, conda_error_type) elif dic['action'] == C.ACTION_CLONE: self.sig_environment_cloned.emit(conda_error, conda_error_type) elif dic['action'] == C.ACTION_REMOVE_ENV: self.sig_environment_removed.emit(conda_error, conda_error_type) self.setup() def _partial_output_ready(self, worker, output, error): """ """ message = None progress = (0, 0) if isinstance(output, dict): progress = (output.get('progress', None), output.get('maxval', None)) name = output.get('name', None) fetch = output.get('fetch', None) if fetch: message = "Downloading <b>{0}</b>...".format(fetch) if name: self._current_action_name = name message = "Linking <b>{0}</b>...".format(name) logger.debug(message) self.update_status(message, progress=progress) def _run_pip_action(self, package_name, action): """ DEPRECATED """ prefix = self.prefix if prefix == self.root_prefix: name = 'root' elif self.api.conda_environment_exists(prefix=prefix): name = osp.basename(prefix) else: name = prefix if action == C.ACTION_REMOVE: msgbox = QMessageBox.question(self, "Remove pip package: " "{0}".format(package_name), "Do you want to proceed?", QMessageBox.Yes | QMessageBox.No) if msgbox == QMessageBox.Yes: self.update_status() worker = self.api.pip_remove(prefix=self.prefix, pkgs=[package_name]) worker.sig_finished.connect(self._pip_process_ready) status = (_('Removing pip package <b>') + package_name + '</b>' + _(' from <i>') + name + '</i>') self.update_status(hide=True, message=status, progress=[0, 0]) def _run_conda_action(self, package_name, action, version, versions, packages_sizes): """ DEPRECATED """ prefix = self.prefix dlg = CondaPackageActionDialog(self, prefix, package_name, action, version, versions, packages_sizes, self._active_channels) if dlg.exec_(): dic = {} self.status = 'Processing' self.update_status(hide=True) self.repaint() ver1 = dlg.label_version.text() ver2 = dlg.combobox_version.currentText() pkg = u'{0}={1}{2}'.format(package_name, ver1, ver2) dep = dlg.checkbox.checkState() state = dlg.checkbox.isEnabled() dlg.close() dic['pkg'] = pkg dic['dep'] = not (dep == 0 and state) dic['action'] = None self._run_conda_process(action, dic) def _run_conda_process(self, action, dic): """ DEPRECTAED """ self._temporal_action_dic = dic # prefix = self.prefix # # if prefix == self.root_prefix: # name = 'root' # elif self.api.conda_environment_exists(prefix=prefix): # name = osp.basename(prefix) # else: # name = prefix if 'pkgs' in dic and 'dep' in dic: dep = dic['dep'] pkgs = dic['pkgs'] if not isinstance(pkgs, list): pkgs = [pkgs] # if (action == C.ACTION_INSTALL or action == C.ACTION_UPGRADE or # action == C.ACTION_DOWNGRADE): # status = _('Installing <b>') + dic['pkg'] + '</b>' # status = status + _(' into <i>') + name + '</i>' # worker = self.api.conda_install(prefix=prefix, pkgs=pkgs, dep=dep, # channels=self._active_channels) # elif action == C.ACTION_REMOVE: # status = (_('Removing <b>') + dic['pkg'] + '</b>' + # _(' from <i>') + name + '</i>') # worker = self.api.conda_remove(pkgs[0], prefix=prefix) # --- Environment management actions name = dic['name'] if action == C.ACTION_CREATE: status = _('Creating environment <b>') + name + '</b>' worker = self.api.conda_create(name=name, pkgs=pkgs, channels=self._active_channels) elif action == C.ACTION_CLONE: clone = dic['clone'] status = (_('Cloning ') + '<i>' + clone + _('</i> into <b>') + name + '</b>') worker = self.api.conda_clone(clone, name=name) elif action == C.ACTION_REMOVE_ENV: status = _('Removing environment <b>') + name + '</b>' worker = self.api.conda_remove(name=name, all_=True) worker.sig_finished.connect(self._conda_process_ready) worker.sig_partial.connect(self._partial_output_ready) self.update_status(hide=True, message=status, progress=None) self._temporal_action_dic = dic return worker # Public API # ------------------------------------------------------------------------- def prepare_model_data(self, packages, apps): """ """ logger.debug('') self._prepare_model_data(output=(packages, apps)) # These should be private methods.... def enable_widgets(self): """ """ self.table.hide_columns() def disable_widgets(self): """ """ self.table.hide_action_columns() def accept_channels_dialog(self): self.button_channels.setFocus() self.button_channels.toggle() def update_actions(self, number_of_actions): """ """ self.button_apply.setVisible(bool(number_of_actions)) self.button_clear.setVisible(bool(number_of_actions)) # --- Non UI API # ------------------------------------------------------------------------- def setup(self, check_updates=False, blacklist=[], metadata={}): """ Setup packages. Main triger method to download repodata, load repodata, prepare and updating the data model. Parameters ---------- check_updates : bool If `True`, checks that the latest repodata is available on the listed channels. If `False`, the data will be loaded from the downloaded files without checking for newer versions. blacklist: list of str List of conda package names to be excluded from the actual package manager view. """ self.sig_packages_busy.emit() if self.busy: logger.debug('Busy...') return else: logger.debug('') if blacklist: self.package_blacklist = [p.lower() for p in blacklist] if metadata: self._metadata = metadata self._current_model_index = self.table.currentIndex() self._current_table_scroll = self.table.verticalScrollBar().value() self.update_status('Updating package index', True) if check_updates: worker = self.api.update_metadata() worker.sig_finished.connect(self._metadata_updated) else: paths = self.api.repodata_files(channels=self._active_channels) self._repodata_updated(paths) def update_domains(self, anaconda_api_url=None, conda_url=None): """ """ logger.debug(str((anaconda_api_url, conda_url))) update = False if anaconda_api_url: if self.conda_api_url != anaconda_api_url: update = True self.conda_api_url = anaconda_api_url self.api.client_set_domain(anaconda_api_url) if conda_url: if self.conda_url != conda_url: update = True self.conda_url = conda_url if update: pass def set_environment(self, name=None, prefix=None): """ This does not update the package manager! """ logger.debug(str((name, prefix))) if prefix and self.api.conda_environment_exists(prefix=prefix): self.prefix = prefix elif name and self.api.conda_environment_exists(name=name): self.prefix = self.get_prefix_envname(name) else: self.prefix = self.root_prefix def set_token(self, token): self.token = token def update_channels(self, channels, active_channels): """ """ logger.debug(str((channels, active_channels))) if sorted(self._active_channels) != sorted(active_channels) or \ sorted(self._channels) != sorted(channels): self._channels = channels self._active_channels = active_channels self.sig_channels_updated.emit(tuple(channels), tuple(active_channels)) self.setup(check_updates=True) def update_style_sheet(self, style_sheet=None, extra_dialogs={}, palette={}): if style_sheet: self.style_sheet = style_sheet self.table.update_style_palette(palette=palette) self.textbox_search.update_style_sheet(style_sheet) self.setStyleSheet(style_sheet) if extra_dialogs: cancel_dialog = extra_dialogs.get('cancel_dialog', None) apply_actions_dialog = extra_dialogs.get('apply_actions_dialog', None) message_box_error = extra_dialogs.get('message_box_error', None) if cancel_dialog: self.cancel_dialog = cancel_dialog if apply_actions_dialog: self.apply_actions_dialog = apply_actions_dialog if message_box_error: self.message_box_error = message_box_error # --- UI API # ------------------------------------------------------------------------- def filter_package(self, value): """ """ self.table.filter_status_changed(value) def show_channels_dialog(self): """ Show the channels dialog. """ button_channels = self.button_channels self.dlg = DialogChannels(self, channels=self._channels, active_channels=self._active_channels, conda_url=self.conda_url) self.dlg.update_style_sheet(style_sheet=self.style_sheet) button_channels.setDisabled(True) self.dlg.sig_channels_updated.connect(self.update_channels) self.dlg.rejected.connect(lambda: button_channels.setEnabled(True)) self.dlg.rejected.connect(button_channels.toggle) self.dlg.rejected.connect(button_channels.setFocus) self.dlg.accepted.connect(self.accept_channels_dialog) geo_tl = button_channels.geometry().topLeft() tl = button_channels.parentWidget().mapToGlobal(geo_tl) x = tl.x() + 2 y = tl.y() + button_channels.height() self.dlg.move(x, y) self.dlg.show() self.dlg.button_add.setFocus() def update_package_index(self): """ """ self.setup(check_updates=True) def search_package(self, text): """ """ self.table.search_string_changed(text) def apply_multiple_actions(self): """ """ logger.debug('') self.conda_errors = [] prefix = self.prefix if prefix == self.root_prefix: name = 'root' elif self.api.conda_environment_exists(prefix=prefix): name = osp.basename(prefix) else: name = prefix actions = self.table.get_actions() if actions is None: return self._multiple_process = deque() pip_actions = actions[C.PIP_PACKAGE] conda_actions = actions[C.CONDA_PACKAGE] pip_remove = pip_actions.get(C.ACTION_REMOVE, []) conda_remove = conda_actions.get(C.ACTION_REMOVE, []) conda_install = conda_actions.get(C.ACTION_INSTALL, []) conda_upgrade = conda_actions.get(C.ACTION_UPGRADE, []) conda_downgrade = conda_actions.get(C.ACTION_DOWNGRADE, []) message = '' template_1 = '<li><b>{0}={1}</b></li>' template_2 = '<li><b>{0}: {1} -> {2}</b></li>' if pip_remove: temp = [template_1.format(i['name'], i['version_to']) for i in pip_remove] message += ('The following pip packages will be removed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_remove: temp = [template_1.format(i['name'], i['version_to']) for i in conda_remove] message += ('<br>The following conda packages will be removed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_install: temp = [template_1.format(i['name'], i['version_to']) for i in conda_install] message += ('<br>The following conda packages will be installed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_downgrade: temp = [template_2.format( i['name'], i['version_from'], i['version_to']) for i in conda_downgrade] message += ('<br>The following conda packages will be downgraded: ' '<ul>' + ''.join(temp) + '</ul>') if conda_upgrade: temp = [template_2.format( i['name'], i['version_from'], i['version_to']) for i in conda_upgrade] message += ('<br>The following conda packages will be upgraded: ' '<ul>' + ''.join(temp) + '</ul>') message += '<br>' if self.apply_actions_dialog: dlg = self.apply_actions_dialog(message, parent=self) dlg.update_style_sheet(style_sheet=self.style_sheet) reply = dlg.exec_() else: reply = QMessageBox.question(self, 'Proceed with the following actions?', message, buttons=QMessageBox.Ok | QMessageBox.Cancel) if reply: # Pip remove for pkg in pip_remove: status = (_('Removing pip package <b>') + pkg['name'] + '</b>' + _(' from <i>') + name + '</i>') pkgs = [pkg['name']] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.pip_remove(prefix=prefix, pkgs=pkgs) self._multiple_process.append([status, trigger()]) # Process conda actions if conda_remove: status = (_('Removing conda packages <b>') + '</b>' + _(' from <i>') + name + '</i>') pkgs = [i['name'] for i in conda_remove] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_remove(pkgs=pkgs, prefix=prefix) self._multiple_process.append([status, trigger()]) if conda_install: pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in conda_install] status = (_('Installing conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install( prefix=prefix, pkgs=pkgs, channels=self._active_channels, token=self.token) self._multiple_process.append([status, trigger()]) # Conda downgrade if conda_downgrade: status = (_('Downgrading conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in conda_downgrade] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install( prefix=prefix, pkgs=pkgs, channels=self._active_channels, token=self.token) self._multiple_process.append([status, trigger()]) # Conda update if conda_upgrade: status = (_('Upgrading conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') pkgs = ['{0}={1}'.format(i['name'], i['version_to']) for i in conda_upgrade] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install( prefix=prefix, pkgs=pkgs, channels=self._active_channels, token=self.token) self._multiple_process.append([status, trigger()]) self._run_multiple_actions() def clear_actions(self): """ """ self.table.clear_actions() def cancel_process(self): """ Allow user to cancel an ongoing process. """ logger.debug(str('process canceled by user.')) if self.busy: dlg = self.cancel_dialog() reply = dlg.exec_() if reply: self.update_status(hide=False, message='Process cancelled') self.api.conda_terminate() self.api.download_requests_terminate() self.api.conda_clear_lock() self.table.clear_actions() self.sig_process_cancelled.emit() else: QDialog.reject(self) def update_status(self, message=None, hide=True, progress=None, env=False): """ Update status bar, progress bar display and widget visibility message : str Message to display in status bar. hide : bool Enable/Disable widgets. progress : [int, int] Show status bar progress. [0, 0] means spinning statusbar. """ self.busy = hide for widget in self.widgets: widget.setDisabled(hide) self.table.verticalScrollBar().setValue(self._current_table_scroll) self.button_apply.setVisible(False) self.button_clear.setVisible(False) self.progress_bar.setVisible(hide) self.button_cancel.setVisible(hide) if message is not None: self.message = message if self.prefix == self.root_prefix: short_env = 'root' # elif self.api.environment_exists(prefix=self.prefix): # short_env = osp.basename(self.prefix) else: short_env = self.prefix if env: self.message = '{0} (<b>{1}</b>)'.format( self.message, short_env, ) self.status_bar.setText(self.message) if progress is not None: current_progress, max_progress = 0, 0 if progress[1]: max_progress = progress[1] if progress[0]: current_progress = progress[0] self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(max_progress) self.progress_bar.setValue(current_progress) else: self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(0) # --- Conda helpers # ------------------------------------------------------------------------- def get_environment_prefix(self): """ Returns the active environment prefix. """ return self.prefix def get_environment_name(self): """ Returns the active environment name if it is located in the default conda environments directory, otherwise it returns the prefix. """ name = osp.basename(self.prefix) if not (name and self.api.environment_exists(name=name)): name = self.prefix return name def get_environments(self): """ Get a list of conda environments located in the default conda environments directory. """ return self.api.conda_get_envs() def get_prefix_envname(self, name): """ Returns the prefix for a given environment by name. """ return self.api.conda_get_prefix_envname(name) def get_package_versions(self, name): """ """ return self.table.source_model.get_package_versions(name) # --- Conda actions # ------------------------------------------------------------------------- def create_environment(self, name=None, prefix=None, packages=['python']): """ """ # If environment exists already? GUI should take care of this # BUT the api call should simply set that env as the env dic = {} dic['name'] = name dic['prefix'] = prefix dic['pkgs'] = packages dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_CREATE return self._run_conda_process(dic['action'], dic) def clone_environment(self, name=None, prefix=None, clone=None): dic = {} dic['name'] = name dic['prefix'] = prefix dic['clone'] = clone dic['pkgs'] = None dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_CLONE return self._run_conda_process(dic['action'], dic) def remove_environment(self, name=None, prefix=None): dic = {} dic['name'] = name dic['pkgs'] = None dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_REMOVE_ENV return self._run_conda_process(dic['action'], dic) # New api def show_login_dialog(self): pass def show_options_menu(self): pass
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None self.btn_save_and_close = None self.btn_close = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data readonly = readonly or not self.data.flags.writeable is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.ndim > 3: self.error(_("Arrays with more than 3 dimensions are not " "supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error(_("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error(_("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget(ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget(ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget(ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect( self.save_and_close_enable) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - '+title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect(self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel(_("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, left_top, bottom_right): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':']*3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText((r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)]*3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index is None: stack_index = self.stack.count() try: self.stack.addWidget(ArrayEditorWidget( self, self.data[tuple(slice_index)])) except IndexError: # Handle arrays of size 0 in one axis self.stack.addWidget(ArrayEditorWidget(self, self.data)) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i']*3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText(('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index]-1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
def setup_ui(self): """Set up Ui of Context Menu.""" self.setStyleSheet(""" #{}{{ min-width:11.29em; }}""".format(self.objectName())) hbl = QHBoxLayout(self) btn = QPushButton(self.name, self) hbl.addWidget(btn) btn.setEnabled(True) btn.setAutoDefault(False) lbl = QLabel(self) hbl.addWidget(lbl) sz_pol = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sz_pol.setHorizontalStretch(1) lbl.setSizePolicy(sz_pol) lbl.setMouseTracking(True) lbl.setAcceptDrops(True) lbl.setTextInteractionFlags(Qt.TextEditorInteraction) self.new_string_signal.connect(lbl.setText) menu = QMenu(btn) btn.setContextMenuPolicy(Qt.CustomContextMenu) btn.setMenu(menu) btn.clicked.connect(btn.showMenu) act = menu.addAction('Get From &File') act.setIcon(qta.icon('mdi.file-download-outline')) act.triggered.connect(self._load_orbit_from_file) act = menu.addAction('Get From &ServConf') act.setIcon(qta.icon('mdi.cloud-download-outline')) act.triggered.connect(self._load_orbit_from_servconf) menu2 = menu.addMenu('Get from &IOC') menu2.setIcon(qta.icon('mdi.download-network-outline')) if self._csorb.acc == 'SI': act = menu2.addAction('&SlowOrb') act.setIcon(qta.icon('mdi.turtle')) act.triggered.connect(_part(self._register_orbit, 'orb')) if self._csorb.isring: act = menu2.addAction('&MTurnOrb') act.setIcon(qta.icon('mdi.alarm-multiple')) act.triggered.connect(_part(self._register_orbit, 'mti')) act = menu2.addAction('S&PassOrb') act.setIcon(qta.icon('mdi.clock-fast')) act.triggered.connect(_part(self._register_orbit, 'sp')) act = menu2.addAction('&RefOrb') act.triggered.connect(_part(self._register_orbit, 'ref')) act = menu2.addAction('&OfflineOrb') act.setIcon(qta.icon('mdi.signal-off')) act.triggered.connect(_part(self._register_orbit, 'off')) act = menu2.addAction('&BPM Offsets') act.setIcon(qta.icon('mdi.currency-sign')) act.triggered.connect(_part(self._register_orbit, 'bpm')) act = menu2.addAction('RespMat') act.setIcon(qta.icon('mdi.matrix')) act.triggered.connect(self._open_matrix_sel) act = menu.addAction('&Edit Orbit') act.setIcon(qta.icon('mdi.table-edit')) act.triggered.connect(self._edit_orbit) if self._csorb.acc == 'SI': act = menu.addAction('Create &Bump') act.setIcon( qta.icon('mdi.chart-bell-curve', scale_factor=1.2, offset=(-0.2, 0.2))) act.triggered.connect(self._create_bump) act = menu.addAction('&Clear') act.setIcon(qta.icon('mdi.delete-empty')) act.triggered.connect(self._reset_orbit) act = menu.addAction('Save To File') act.setIcon(qta.icon('mdi.file-upload-outline')) act.triggered.connect(self._save_orbit_to_file) act = menu.addAction('Save To ServConf') act.setIcon(qta.icon('mdi.cloud-upload-outline')) act.triggered.connect(self._save_orbit_to_servconf)
class ParamSweepWidget(QDialog): """ A dialog box containing a workflow finished message. Also includes a button to open the output folder and a button to close the dialog box Params: output_folder (Path): The output folder to open when the corresponding button is clicked by the user. """ def __init__(self, param_set: Dict[str, Any], step_number, controller): super().__init__() # Track UI elements self.live_count: QLabel = None self.progress_bar: QProgressBar = None self.inputs: Dict[str, QFrame] = dict() self.default_sweep_values: Dict = dict() self.layout: QVBoxLayout = QVBoxLayout() # State self.controller = controller self.step_number: int = step_number self.param_set: Dict[str, Any] = param_set rows: List[FormRow] = self._create_sweep_ui() # Format UI on init self.layout.setContentsMargins(0, 0, 0, 0) self.layout.addWidget(self._create_buttons()) self.setLayout(Form(rows)) self.setStyleSheet(get_stylesheet(self.controller.viewer.get_theme())) self.setWindowTitle("Parameter Sweep") def _create_sweep_ui(self) -> List[FormRow]: """ Populate the sweep widget with this workflow step's parameters and their default values as well as other UI elements needed to run a sweep. Params: none """ rows: List[FormRow] = list() # add ui elements in order rows.append(FormRow("", widget=self.create_sweep_headers())) # convert parameter set to form rows default_params: Dict = self.controller.model.active_workflow.workflow_definition.steps[ self.step_number].function.parameters if self.param_set: for key, value in self.param_set.items(): # sometimes multiple unique params are in one list, need to separate out for UI if isinstance(value, list): if not isinstance(value[0], str): i = 1 for _ in value: sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get default value min_value: int = default_params[key][i - 1].min_value max_value: int = default_params[key][i - 1].max_value step_size: float = (max_value - min_value) / 2 # Create UI Elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) # Reset button for row (reset to default values) reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") # pass the key and value as values for calling function later on reset_button.clicked.connect( partial( lambda k, val: self._reset_row_to_default( k, val), key, i)) sweep_inputs.layout().addWidget(reset_button) # store the index of this parameter in its list appended to the parameter key self.inputs[key + str(i)] = sweep_inputs rows.append( FormRow(f"{key} {i}", widget=sweep_inputs)) i = i + 1 else: # most params are single entries in the param dictionary # for params that are a dropdown if default_params[key][0].widget_type.name == "DROPDOWN": dropdown = QComboBox() dropdown.setStyleSheet( "QComboBox { combobox-popup: 0; }") dropdown.addItems(default_params[key][0].options) self.inputs[key] = dropdown rows.append(FormRow(key, widget=dropdown)) else: # for typical sweep params sweep_inputs: QFrame = QFrame() sweep_inputs.setLayout(QHBoxLayout()) # get the default values min_value: int = default_params[key][0].min_value max_value: int = default_params[key][0].max_value step_size: float = (max_value - min_value) / 2 # Create UI elements and populate with default values min_input: QLineEdit = QLineEdit() min_input.setText(str(min_value)) min_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(min_input) max_input: QLineEdit = QLineEdit() max_input.setText(str(max_value)) max_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(max_input) step_input: QLineEdit = QLineEdit() step_input.setText(str(step_size)) step_input.editingFinished.connect( self._on_change_textbox) sweep_inputs.layout().addWidget(step_input) self.inputs[key] = sweep_inputs # Create button to reset back to default values reset_button: QPushButton = QPushButton("reset") reset_button.setStyleSheet("border: none;") reset_button.clicked.connect( lambda: self._reset_row_to_default(key)) sweep_inputs.layout().addWidget(reset_button) rows.append(FormRow(key, widget=sweep_inputs)) # Grab user input as str def_params_values: List[List[str]] = self.grab_ui_values( grab_combo=False) # Display how many images will be create with this sweep def_count: int = self.get_live_count(def_params_values) self.live_count = QLabel(f"{def_count} images will be created") self.live_count.setAlignment(qtpy.QtCore.Qt.AlignCenter) # Create progress bar with length of sweep self.create_progress_bar(bar_len=def_count) rows.append(FormRow("", widget=self.live_count)) rows.append(FormRow("", widget=self.progress_bar)) rows.append(FormRow("", widget=self._create_buttons())) return rows def _create_buttons(self) -> QFrame: """ Creates buttons for the bottom of the dialog box, one for opening the output folder, and one for closing the dialog box Params: None Returns: (QFrame): A QFrame that has two horizontally laid out buttons, first button is Open output directory, second button is close. """ buttons: QFrame = QFrame() buttons.setLayout(QHBoxLayout()) self.run_sweep_button = QPushButton("Start Sweep") self.run_sweep_button.setToolTip( "Start sweep using the selected napari layer as the input image.") self.run_sweep_button.clicked.connect(self._run_sweep) self.run_sweep_button.setAutoDefault(False) close_button: QPushButton = QPushButton("Cancel") close_button.setToolTip("Cancel an active parameter sweep.") close_button.clicked.connect(self.cancel) close_button.setAutoDefault(False) buttons.layout().addWidget(self.run_sweep_button) buttons.layout().addWidget(close_button) return buttons def _run_sweep(self) -> None: """ Initiate a sweep (called when run sweep button is pressed) with the values provided in the UI Params: none """ inputs: List[List[str]] = self.grab_ui_values(grab_combo=False) count: int = self.get_live_count(inputs) if count > 20: # warn if too many images will be generated if self.warn_images_created(count) == 1024: self.set_run_in_progress() self.controller.run_step_sweep(self, self.grab_ui_values()) else: self.set_run_in_progress() self.controller.run_step_sweep(self, self.grab_ui_values()) def sanitize_ui_inputs(self, ui_inputs: List[List[str]]) -> None: """ Check that all user inputs in the UI (passed as str) can be converted to a valid float Params: ui_inputs (List[List[str]]): inputs recieved from the UI as str """ for i in ui_inputs: for j in i: try: # ensure user input (passed as str) is a valid number float(j) except ValueError: raise ValueError( "Please enter a single number or the min:step:max notation for sweeps" ) def warn_images_created(self, count: int) -> None: """ Create a popup to warn a user that they will be creating a large number of layers by running a sweep. Params: count (int): number of images that user will create for warning """ message: QMessageBox = QMessageBox() message.setText(f"{int(count)} result image layers will be created.") message.setStyleSheet( get_stylesheet(self.controller.viewer.get_theme())) message.setWindowTitle("Running Sweep") message.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) return message.exec_() def update_live_count(self, count: int) -> None: """ Update the Live count on the param sweep widget Params: count (int): new live count to update with """ if self.live_count: self.live_count.setText(f"{count } images will be created") def get_live_count(self, inputs: List[List[str]]) -> int: """ Calculate how many images will be created with the user provided values for this sweep Params: inputs (List[List[str]]): user inputs from the paramsweepwidget to calculate how many total result layers will be created """ # make sure all user inputs are valid numbers self.sanitize_ui_inputs(inputs) length: int = 1 for sweeps in inputs: # multiplying the result layers for all parameters will give total result layers # (ex: if param1 creates 5 combinations and param2 creates 5 combinations, there will be # 25 result layers created 5x5=25) length = length * self.get_sweep_len(float( sweeps[0]), float(sweeps[1]), float(sweeps[2])) return length def get_sweep_len(self, min: float, step: float, max: float) -> int: """ Calculate how many result layers will be created for one parameter's sweep values Params: min (float): starting range of sweep provided by user step (float): increment value of sweep provided by user max (float): end value of sweep provided by user """ i: float = min count: int = 0 while i <= max: i = i + step count = count + 1 return count def grab_ui_values(self, grab_combo=True) -> List[List[str]]: """ Update the Live count on the param sweep widget Params: count (int): new live count to update with """ inputs: List[List[str]] = list() for widget in self.inputs.values(): # grab values from combobox (when calling run_sweep function) if grab_combo: # ui is min, max, step # transform into min, step, max try: # is a set of numbers for sweep inputs.append([ widget.children()[1].text(), widget.children()[3].text(), widget.children()[2].text() ]) except IndexError: # is a combobox(string parameters) inputs.append(widget.currentText()) except AttributeError: inputs.append(widget.currentText()) else: # do not grab values from combobox (for checking inputs and getting size of sweeps) try: inputs.append([ widget.children()[1].text(), widget.children()[3].text(), widget.children()[2].text() ]) except Exception: pass return inputs def create_progress_bar(self, bar_len: int = 10) -> QProgressBar: """ Create a progress bar with the given length Params: bar_len (int): length of progress bar to be created """ self.progress_bar: QProgressBar = QProgressBar() self.progress_bar.setRange(0, bar_len) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(False) return self.progress_bar def update_progress_bar_len(self, new_len: int) -> None: """ Update the progress bar so it has the given length Params: new_len (int): new length to update progress bar """ if self.progress_bar: self.progress_bar.setRange(0, new_len) self.progress_bar.setValue(0) def increment_progress_bar(self) -> None: """ Increment the progress bar by one step Params: none """ if self.controller.run_lock: self.progress_bar.setValue(self.progress_bar.value() + 1) def reset_progress_bar(self) -> None: """ Reset the progress bar to 0 Params: none """ self.progress_bar.setValue(0) def set_progress_bar(self, val: int) -> None: """ Set the progress bar to the value provided Params: val: value to set the progess bar to """ self.progress_bar.setValue(val) def create_sweep_headers(self) -> QFrame: """ Create headers for the sweep UI Params: none """ # headers header: QFrame = QFrame() header.setLayout(QHBoxLayout()) label: QLabel = QLabel("min") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) label: QLabel = QLabel("max") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) label: QLabel = QLabel("step size") label.setAlignment(Qt.AlignCenter) header.layout().addWidget(label) return header def _on_change_textbox(self) -> None: """ Event listener called when textboxes in widget are edited Params: none """ # grab new values inputs: List[List[str]] = self.grab_ui_values(grab_combo=False) # calculate new count new_count: int = self.get_live_count(inputs) # update ui self.update_live_count(new_count) self.update_progress_bar_len(new_count) def set_run_in_progress(self) -> None: """ function called when a run is in progress Params: none """ self.run_sweep_button.setText("Cancel") def set_run_finished(self) -> None: """ function called when a run is finished Params: none """ self.run_sweep_button.setText("Run Sweep") self.run_sweep_button.clicked.connect(self._run_sweep) def cancel(self) -> None: """ Cancel the current sweep Params: none """ if self.controller.run_lock: # if running, cancel self.controller.cancel_run_all() else: # if not running, close window self.close() def _reset_row_to_default(self, key_of_row: str, list_index: int = None): """ Reset a parameter row to its default values Params: key_of_row (str): Name of row in ui to change back to default values list_index (int): index of parameter if there are multiple with the same name """ default_param_set: Dict = self.controller.model.active_workflow.workflow_definition.steps[ self.step_number].function.parameters if list_index: input_boxes: List = self.inputs[key_of_row + str(list_index)].children() else: input_boxes = self.inputs[key_of_row].children() if list_index: default_params = default_param_set[key_of_row][list_index - 1] else: default_params = default_param_set[key_of_row][0] # reset min value input_boxes[1].setText(str(default_params.min_value)) # reset max value input_boxes[2].setText(str(default_params.max_value)) # reset step_size value input_boxes[3].setText( str((default_params.max_value - default_params.min_value) / 2)) updated_values: List[List[str]] = self.grab_ui_values(grab_combo=False) self.update_live_count(self.get_live_count(updated_values))
class OpenTourDialog(QDialog): """Initial widget with tour.""" ICON_SCALE_FACTOR = 0.7 if MAC else 0.75 TITLE_FONT_SIZE = '19pt' if MAC else '16pt' CONTENT_FONT_SIZE = '15pt' if MAC else '12pt' BUTTONS_FONT_SIZE = '15pt' if MAC else '13pt' BUTTONS_PADDING = '6px' if MAC else '4px 10px' def __init__(self, parent, tour_function): super().__init__(parent) if MAC: flags = (self.windowFlags() | Qt.WindowStaysOnTopHint & ~Qt.WindowContextHelpButtonHint) else: flags = self.windowFlags() & ~Qt.WindowContextHelpButtonHint self.setWindowFlags(flags) self.tour_function = tour_function # Image images_layout = QHBoxLayout() icon_filename = 'tour-spyder-logo' image_path = get_image_path(icon_filename) image = QPixmap(image_path) image_label = QLabel() image_height = image.height() * self.ICON_SCALE_FACTOR image_width = image.width() * self.ICON_SCALE_FACTOR image = image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image_label.setPixmap(image) images_layout.addStretch() images_layout.addWidget(image_label) images_layout.addStretch() if MAC: images_layout.setContentsMargins(0, -5, 20, 0) else: images_layout.setContentsMargins(0, -8, 35, 0) # Label tour_label_title = QLabel(_("Welcome to Spyder!")) tour_label_title.setStyleSheet(f"font-size: {self.TITLE_FONT_SIZE}") tour_label_title.setWordWrap(True) tour_label = QLabel( _("Check out our interactive tour to " "explore some of Spyder's panes and features.")) tour_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}") tour_label.setWordWrap(True) tour_label.setFixedWidth(340) # Buttons buttons_layout = QHBoxLayout() dialog_tour_color = QStylePalette.COLOR_BACKGROUND_2 start_tour_color = QStylePalette.COLOR_ACCENT_2 start_tour_hover = QStylePalette.COLOR_ACCENT_3 start_tour_pressed = QStylePalette.COLOR_ACCENT_4 dismiss_tour_color = QStylePalette.COLOR_BACKGROUND_4 dismiss_tour_hover = QStylePalette.COLOR_BACKGROUND_5 dismiss_tour_pressed = QStylePalette.COLOR_BACKGROUND_6 font_color = QStylePalette.COLOR_TEXT_1 self.launch_tour_button = QPushButton(_('Start tour')) self.launch_tour_button.setStyleSheet( ("QPushButton {{ " "background-color: {background_color};" "border-color: {border_color};" "font-size: {font_size};" "color: {font_color};" "padding: {padding}}}" "QPushButton:hover:!pressed {{ " "background-color: {color_hover}}}" "QPushButton:pressed {{ " "background-color: {color_pressed}}}").format( background_color=start_tour_color, border_color=start_tour_color, font_size=self.BUTTONS_FONT_SIZE, font_color=font_color, padding=self.BUTTONS_PADDING, color_hover=start_tour_hover, color_pressed=start_tour_pressed)) self.launch_tour_button.setAutoDefault(False) self.dismiss_button = QPushButton(_('Dismiss')) self.dismiss_button.setStyleSheet( ("QPushButton {{ " "background-color: {background_color};" "border-color: {border_color};" "font-size: {font_size};" "color: {font_color};" "padding: {padding}}}" "QPushButton:hover:!pressed {{ " "background-color: {color_hover}}}" "QPushButton:pressed {{ " "background-color: {color_pressed}}}").format( background_color=dismiss_tour_color, border_color=dismiss_tour_color, font_size=self.BUTTONS_FONT_SIZE, font_color=font_color, padding=self.BUTTONS_PADDING, color_hover=dismiss_tour_hover, color_pressed=dismiss_tour_pressed)) self.dismiss_button.setAutoDefault(False) buttons_layout.addStretch() buttons_layout.addWidget(self.launch_tour_button) if not MAC: buttons_layout.addSpacing(10) buttons_layout.addWidget(self.dismiss_button) layout = QHBoxLayout() layout.addLayout(images_layout) label_layout = QVBoxLayout() label_layout.addWidget(tour_label_title) if not MAC: label_layout.addSpacing(3) label_layout.addWidget(tour_label) else: label_layout.addWidget(tour_label) label_layout.addSpacing(10) vertical_layout = QVBoxLayout() if not MAC: vertical_layout.addStretch() vertical_layout.addLayout(label_layout) vertical_layout.addSpacing(20) vertical_layout.addLayout(buttons_layout) vertical_layout.addStretch() else: vertical_layout.addLayout(label_layout) vertical_layout.addLayout(buttons_layout) general_layout = QHBoxLayout() if not MAC: general_layout.addStretch() general_layout.addLayout(layout) general_layout.addSpacing(1) general_layout.addLayout(vertical_layout) general_layout.addStretch() else: general_layout.addLayout(layout) general_layout.addLayout(vertical_layout) self.setLayout(general_layout) self.launch_tour_button.clicked.connect(self._start_tour) self.dismiss_button.clicked.connect(self.close) self.setStyleSheet(f"background-color:{dialog_tour_color}") self.setContentsMargins(18, 40, 18, 40) if not MAC: self.setFixedSize(640, 280) def _start_tour(self): self.close() self.tour_function()
class CondaPackagesWidget(QWidget): """ Conda Packages Widget. """ # Location of updated repo.json files from continuum/binstar CONDA_CONF_PATH = get_conf_path('repo') # Location of continuum/anaconda default repos shipped with conda-manager DATA_PATH = get_module_data_path() # file inside DATA_PATH with metadata for conda packages DATABASE_FILE = 'packages.ini' sig_worker_ready = Signal() sig_packages_ready = Signal() sig_environment_created = Signal(object, object) sig_environment_removed = Signal(object, object) sig_environment_cloned = Signal(object, object) sig_channels_updated = Signal(tuple, tuple) # channels, active_channels sig_process_cancelled = Signal() sig_next_focus = Signal() sig_packages_busy = Signal() def __init__(self, parent, name=None, prefix=None, channels=(), active_channels=(), conda_url='https://conda.anaconda.org', conda_api_url='https://api.anaconda.org', setup=True, data_directory=None, extra_metadata={}): super(CondaPackagesWidget, self).__init__(parent) # Check arguments: active channels, must be witbhin channels for ch in active_channels: if ch not in channels: raise Exception("'active_channels' must be also within " "'channels'") if data_directory is None: data_directory = self.CONDA_CONF_PATH self._parent = parent self._current_action_name = '' self._hide_widgets = False self._metadata = extra_metadata # From repo.continuum self._metadata_links = {} # Bundled metadata self.api = ManagerAPI() self.busy = False self.data_directory = data_directory self.conda_url = conda_url self.conda_api_url = conda_api_url self.name = name self.package_blacklist = [] self.prefix = prefix self.root_prefix = self.api.ROOT_PREFIX self.style_sheet = None self.message = '' self.apply_actions_dialog = None self.conda_errors = [] self.message_box_error = None self.token = None if channels: self._channels = channels self._active_channels = active_channels else: self._channels = self.api.conda_get_condarc_channels() self._active_channels = self._channels[:] try: import spyderlib.utils.icon_manager as ima icon_options = ima.icon('tooloptions') except Exception: import qtawesome as qta icon_options = qta.icon('fa.cog') # Widgets self.cancel_dialog = ClosePackageManagerDialog self.bbox = QDialogButtonBox(Qt.Horizontal) self.button_cancel = QPushButton('Cancel') self.button_channels = QPushButton(_('Channels')) self.button_ok = QPushButton(_('Ok')) self.button_update = QPushButton(_('Update index...')) self.button_apply = QPushButton(_('Apply')) self.button_clear = QPushButton(_('Clear')) self.button_options = QToolButton() self.combobox_filter = DropdownPackageFilter(self) self.frame_top = FramePackageTop() self.frame_bottom = FramePackageTop() self.progress_bar = ProgressBarPackage(self) self.status_bar = LabelPackageStatus(self) self.table = TableCondaPackages(self) self.textbox_search = LineEditSearch(self) self.widgets = [ self.button_update, self.button_channels, self.combobox_filter, self.textbox_search, self.table, self.button_ok, self.button_apply, self.button_clear, self.button_options ] self.table_first_row = FirstRowWidget( widget_before=self.textbox_search) self.table_last_row = LastRowWidget(widgets_after=[ self.button_apply, self.button_clear, self.button_cancel, self.combobox_filter ]) # Widget setup self.button_options.setPopupMode(QToolButton.InstantPopup) self.button_options.setIcon(icon_options) self.button_options.setAutoRaise(True) max_height = self.status_bar.fontMetrics().height() max_width = self.textbox_search.fontMetrics().width('M' * 23) self.bbox.addButton(self.button_ok, QDialogButtonBox.ActionRole) self.button_ok.setAutoDefault(True) self.button_ok.setDefault(True) self.button_ok.setMaximumSize(QSize(0, 0)) self.button_ok.setVisible(False) self.combobox_filter.addItems([k for k in C.COMBOBOX_VALUES_ORDERED]) self.combobox_filter.setMinimumWidth(120) self.progress_bar.setMaximumHeight(max_height * 1.2) self.progress_bar.setMaximumWidth(max_height * 12) self.progress_bar.setTextVisible(False) self.progress_bar.setVisible(False) self.setMinimumSize(QSize(480, 300)) self.setWindowTitle(_("Conda Package Manager")) self.status_bar.setFixedHeight(max_height * 1.5) self.textbox_search.setMaximumWidth(max_width) self.textbox_search.setPlaceholderText('Search Packages') self.table_first_row.setMaximumHeight(0) self.table_last_row.setMaximumHeight(0) self.table_last_row.setVisible(False) self.table_first_row.setVisible(False) # Layout top_layout = QHBoxLayout() top_layout.addWidget(self.combobox_filter) top_layout.addWidget(self.button_channels) top_layout.addWidget(self.button_update) top_layout.addWidget(self.textbox_search) top_layout.addStretch() top_layout.addWidget(self.button_options) middle_layout = QVBoxLayout() middle_layout.addWidget(self.table_first_row) middle_layout.addWidget(self.table) middle_layout.addWidget(self.table_last_row) bottom_layout = QHBoxLayout() bottom_layout.addWidget(self.status_bar) bottom_layout.addStretch() bottom_layout.addWidget(self.progress_bar) bottom_layout.addWidget(self.button_cancel) bottom_layout.addWidget(self.button_apply) bottom_layout.addWidget(self.button_clear) layout = QVBoxLayout(self) layout.addLayout(top_layout) layout.addLayout(middle_layout) layout.addLayout(bottom_layout) layout.addSpacing(6) self.setLayout(layout) self.setTabOrder(self.combobox_filter, self.button_channels) self.setTabOrder(self.button_channels, self.button_update) self.setTabOrder(self.button_update, self.textbox_search) self.setTabOrder(self.textbox_search, self.table_first_row) self.setTabOrder(self.table, self.table_last_row) self.setTabOrder(self.table_last_row, self.button_apply) self.setTabOrder(self.button_apply, self.button_clear) self.setTabOrder(self.button_clear, self.button_cancel) # Signals and slots self.api.sig_repodata_updated.connect(self._repodata_updated) self.combobox_filter.currentIndexChanged.connect(self.filter_package) self.button_apply.clicked.connect(self.apply_multiple_actions) self.button_clear.clicked.connect(self.clear_actions) self.button_cancel.clicked.connect(self.cancel_process) self.button_channels.clicked.connect(self.show_channels_dialog) self.button_update.clicked.connect(self.update_package_index) self.textbox_search.textChanged.connect(self.search_package) self.table.sig_conda_action_requested.connect(self._run_conda_action) self.table.sig_actions_updated.connect(self.update_actions) self.table.sig_pip_action_requested.connect(self._run_pip_action) self.table.sig_status_updated.connect(self.update_status) self.table.sig_next_focus.connect(self.table_last_row.handle_tab) self.table.sig_previous_focus.connect( lambda: self.table_first_row.widget_before.setFocus()) self.table_first_row.sig_enter_first.connect(self._handle_tab_focus) self.table_last_row.sig_enter_last.connect(self._handle_backtab_focus) # Setup self.api.client_set_domain(conda_api_url) self.api.set_data_directory(self.data_directory) self._load_bundled_metadata() self.update_actions(0) if setup: self.set_environment(name=name, prefix=prefix) self.setup() # --- Helpers # ------------------------------------------------------------------------- def _handle_tab_focus(self): self.table.setFocus() if self.table.proxy_model: index = self.table.proxy_model.index(0, 0) self.table.setCurrentIndex(index) def _handle_backtab_focus(self): self.table.setFocus() if self.table.proxy_model: row = self.table.proxy_model.rowCount() - 1 index = self.table.proxy_model.index(row, 0) self.table.setCurrentIndex(index) # --- Callbacks # ------------------------------------------------------------------------- def _load_bundled_metadata(self): """ """ logger.debug('') parser = cp.ConfigParser() db_file = CondaPackagesWidget.DATABASE_FILE with open(osp.join(self.DATA_PATH, db_file)) as f: parser.readfp(f) for name in parser.sections(): metadata = {} for key, data in parser.items(name): metadata[key] = data self._metadata_links[name] = metadata def _setup_packages(self, worker, data, error): """ """ if error: logger.error(error) else: logger.debug('') combobox_index = self.combobox_filter.currentIndex() status = C.PACKAGE_STATUS[combobox_index] packages = worker.packages # Remove blacklisted packages for package in self.package_blacklist: if package in packages: packages.pop(package) for i, row in enumerate(data): if package == data[i][C.COL_NAME]: data.pop(i) self.table.setup_model(packages, data, self._metadata_links) self.combobox_filter.setCurrentIndex(combobox_index) self.filter_package(status) if self._current_model_index: self.table.setCurrentIndex(self._current_model_index) self.table.verticalScrollBar().setValue(self._current_table_scroll) if error: self.update_status(error, False) self.sig_packages_ready.emit() self.table.setFocus() def get_logged_user_list_channels(self): channels = [] for ch in self._active_channels: if self.conda_url in ch and 'repo.continuum' not in ch: channel = ch.split('/')[-1] channels.append(channel) return channels def _prepare_model_data(self, worker=None, output=None, error=None): """ """ if error: logger.error(error) else: logger.debug('') packages, apps = output # worker = self.api.pip_list(prefix=self.prefix) # worker.sig_finished.connect(self._pip_list_ready) logins = self.get_logged_user_list_channels() worker = self.api.client_multi_packages(logins=logins, access='private') worker.sig_finished.connect(self._user_private_packages_ready) worker.packages = packages worker.apps = apps def _user_private_packages_ready(self, worker, output, error): if error: logger.error(error) else: logger.debug('') packages = worker.packages apps = worker.apps worker = self.api.pip_list(prefix=self.prefix) worker.sig_finished.connect(self._pip_list_ready) worker.packages = packages worker.apps = apps # private_packages = {} # if output: # all_private_packages = output # for item in all_private_packages: # name = item.get('name', '') # public = item.get('public', True) # package_types = item.get('package_types', []) # latest_version = item.get('latest_version', '') # if name and not public and 'conda' in package_types: # private_packages[name] = {'versions': item.get('versions', []), # 'app_entry': {}, # 'type': {}, # 'size': {}, # 'latest_version': latest_version, # } worker.private_packages = output def _pip_list_ready(self, worker, pip_packages, error): """ """ if error: logger.error(error) else: logger.debug('') packages = worker.packages private_packages = worker.private_packages linked_packages = self.api.conda_linked(prefix=self.prefix) data = self.api.client_prepare_packages_data(packages, linked_packages, pip_packages, private_packages) combobox_index = self.combobox_filter.currentIndex() status = C.PACKAGE_STATUS[combobox_index] # Remove blacklisted packages for package in self.package_blacklist: if package in packages: packages.pop(package) for i, row in enumerate(data): if package == data[i][C.COL_NAME]: data.pop(i) self.table.setup_model(packages, data, self._metadata_links) self.combobox_filter.setCurrentIndex(combobox_index) self.filter_package(status) if self._current_model_index: self.table.setCurrentIndex(self._current_model_index) self.table.verticalScrollBar().setValue(self._current_table_scroll) if error: self.update_status(str(error), False) self.sig_packages_ready.emit() self.table.setFocus() def _repodata_updated(self, paths): """ """ worker = self.api.client_load_repodata(paths, extra_data={}, metadata=self._metadata) worker.paths = paths worker.sig_finished.connect(self._prepare_model_data) def _metadata_updated(self, worker, path, error): """ """ if error: logger.error(error) else: logger.debug('') if path and osp.isfile(path): with open(path, 'r') as f: data = f.read() try: self._metadata = json.loads(data) except Exception: self._metadata = {} else: self._metadata = {} self.api.update_repodata(self._channels) # --- # ------------------------------------------------------------------------- def _run_multiple_actions(self, worker=None, output=None, error=None): """ """ logger.error(str(error)) if output and isinstance(output, dict): conda_error_type = output.get('error_type', None) conda_error = output.get('error', None) if conda_error_type or conda_error: self.conda_errors.append((conda_error_type, conda_error)) logger.error((conda_error_type, conda_error)) if self._multiple_process: status, func = self._multiple_process.popleft() self.update_status(status) worker = func() worker.sig_finished.connect(self._run_multiple_actions) worker.sig_partial.connect(self._partial_output_ready) else: if self.conda_errors and self.message_box_error: text = "The following errors occured:" error = '' for conda_error in self.conda_errors: error += str(conda_error[0]) + ':\n' error += str(conda_error[1]) + '\n\n' dlg = self.message_box_error(text=text, error=error, title='Conda process error') dlg.setMinimumWidth(400) dlg.exec_() self.update_status('', hide=False) self.setup() def _pip_process_ready(self, worker, output, error): """ """ if error is not None: status = _('there was an error') self.update_status(hide=False, message=status) else: self.update_status(hide=True) self.setup() def _conda_process_ready(self, worker, output, error): """ """ if error is not None: status = _('there was an error') self.update_status(hide=False, message=status) else: self.update_status(hide=True) conda_error = None conda_error_type = None if output and isinstance(output, dict): conda_error_type = output.get('error_type') conda_error = output.get('error') if conda_error_type or conda_error: logger.error((conda_error_type, conda_error)) dic = self._temporal_action_dic if dic['action'] == C.ACTION_CREATE: self.sig_environment_created.emit(conda_error, conda_error_type) elif dic['action'] == C.ACTION_CLONE: self.sig_environment_cloned.emit(conda_error, conda_error_type) elif dic['action'] == C.ACTION_REMOVE_ENV: self.sig_environment_removed.emit(conda_error, conda_error_type) self.setup() def _partial_output_ready(self, worker, output, error): """ """ message = None progress = (0, 0) if isinstance(output, dict): progress = (output.get('progress', None), output.get('maxval', None)) name = output.get('name', None) fetch = output.get('fetch', None) if fetch: message = "Downloading <b>{0}</b>...".format(fetch) if name: self._current_action_name = name message = "Linking <b>{0}</b>...".format(name) logger.debug(message) self.update_status(message, progress=progress) def _run_pip_action(self, package_name, action): """ DEPRECATED """ prefix = self.prefix if prefix == self.root_prefix: name = 'root' elif self.api.conda_environment_exists(prefix=prefix): name = osp.basename(prefix) else: name = prefix if action == C.ACTION_REMOVE: msgbox = QMessageBox.question( self, "Remove pip package: " "{0}".format(package_name), "Do you want to proceed?", QMessageBox.Yes | QMessageBox.No) if msgbox == QMessageBox.Yes: self.update_status() worker = self.api.pip_remove(prefix=self.prefix, pkgs=[package_name]) worker.sig_finished.connect(self._pip_process_ready) status = (_('Removing pip package <b>') + package_name + '</b>' + _(' from <i>') + name + '</i>') self.update_status(hide=True, message=status, progress=[0, 0]) def _run_conda_action(self, package_name, action, version, versions, packages_sizes): """ DEPRECATED """ prefix = self.prefix dlg = CondaPackageActionDialog(self, prefix, package_name, action, version, versions, packages_sizes, self._active_channels) if dlg.exec_(): dic = {} self.status = 'Processing' self.update_status(hide=True) self.repaint() ver1 = dlg.label_version.text() ver2 = dlg.combobox_version.currentText() pkg = u'{0}={1}{2}'.format(package_name, ver1, ver2) dep = dlg.checkbox.checkState() state = dlg.checkbox.isEnabled() dlg.close() dic['pkg'] = pkg dic['dep'] = not (dep == 0 and state) dic['action'] = None self._run_conda_process(action, dic) def _run_conda_process(self, action, dic): """ DEPRECTAED """ self._temporal_action_dic = dic # prefix = self.prefix # # if prefix == self.root_prefix: # name = 'root' # elif self.api.conda_environment_exists(prefix=prefix): # name = osp.basename(prefix) # else: # name = prefix if 'pkgs' in dic and 'dep' in dic: dep = dic['dep'] pkgs = dic['pkgs'] if not isinstance(pkgs, list): pkgs = [pkgs] # if (action == C.ACTION_INSTALL or action == C.ACTION_UPGRADE or # action == C.ACTION_DOWNGRADE): # status = _('Installing <b>') + dic['pkg'] + '</b>' # status = status + _(' into <i>') + name + '</i>' # worker = self.api.conda_install(prefix=prefix, pkgs=pkgs, dep=dep, # channels=self._active_channels) # elif action == C.ACTION_REMOVE: # status = (_('Removing <b>') + dic['pkg'] + '</b>' + # _(' from <i>') + name + '</i>') # worker = self.api.conda_remove(pkgs[0], prefix=prefix) # --- Environment management actions name = dic['name'] if action == C.ACTION_CREATE: status = _('Creating environment <b>') + name + '</b>' worker = self.api.conda_create(name=name, pkgs=pkgs, channels=self._active_channels) elif action == C.ACTION_CLONE: clone = dic['clone'] status = (_('Cloning ') + '<i>' + clone + _('</i> into <b>') + name + '</b>') worker = self.api.conda_clone(clone, name=name) elif action == C.ACTION_REMOVE_ENV: status = _('Removing environment <b>') + name + '</b>' worker = self.api.conda_remove(name=name, all_=True) worker.sig_finished.connect(self._conda_process_ready) worker.sig_partial.connect(self._partial_output_ready) self.update_status(hide=True, message=status, progress=None) self._temporal_action_dic = dic return worker # Public API # ------------------------------------------------------------------------- def prepare_model_data(self, packages, apps): """ """ logger.debug('') self._prepare_model_data(output=(packages, apps)) # These should be private methods.... def enable_widgets(self): """ """ self.table.hide_columns() def disable_widgets(self): """ """ self.table.hide_action_columns() def accept_channels_dialog(self): self.button_channels.setFocus() self.button_channels.toggle() def update_actions(self, number_of_actions): """ """ self.button_apply.setVisible(bool(number_of_actions)) self.button_clear.setVisible(bool(number_of_actions)) # --- Non UI API # ------------------------------------------------------------------------- def setup(self, check_updates=False, blacklist=[], metadata={}): """ Setup packages. Main triger method to download repodata, load repodata, prepare and updating the data model. Parameters ---------- check_updates : bool If `True`, checks that the latest repodata is available on the listed channels. If `False`, the data will be loaded from the downloaded files without checking for newer versions. blacklist: list of str List of conda package names to be excluded from the actual package manager view. """ self.sig_packages_busy.emit() if self.busy: logger.debug('Busy...') return else: logger.debug('') if blacklist: self.package_blacklist = [p.lower() for p in blacklist] if metadata: self._metadata = metadata self._current_model_index = self.table.currentIndex() self._current_table_scroll = self.table.verticalScrollBar().value() self.update_status('Updating package index', True) if check_updates: worker = self.api.update_metadata() worker.sig_finished.connect(self._metadata_updated) else: paths = self.api.repodata_files(channels=self._active_channels) self._repodata_updated(paths) def update_domains(self, anaconda_api_url=None, conda_url=None): """ """ logger.debug(str((anaconda_api_url, conda_url))) update = False if anaconda_api_url: if self.conda_api_url != anaconda_api_url: update = True self.conda_api_url = anaconda_api_url self.api.client_set_domain(anaconda_api_url) if conda_url: if self.conda_url != conda_url: update = True self.conda_url = conda_url if update: pass def set_environment(self, name=None, prefix=None): """ This does not update the package manager! """ logger.debug(str((name, prefix))) if prefix and self.api.conda_environment_exists(prefix=prefix): self.prefix = prefix elif name and self.api.conda_environment_exists(name=name): self.prefix = self.get_prefix_envname(name) else: self.prefix = self.root_prefix def set_token(self, token): self.token = token def update_channels(self, channels, active_channels): """ """ logger.debug(str((channels, active_channels))) if sorted(self._active_channels) != sorted(active_channels) or \ sorted(self._channels) != sorted(channels): self._channels = channels self._active_channels = active_channels self.sig_channels_updated.emit(tuple(channels), tuple(active_channels)) self.setup(check_updates=True) def update_style_sheet(self, style_sheet=None, extra_dialogs={}, palette={}): if style_sheet: self.style_sheet = style_sheet self.table.update_style_palette(palette=palette) self.textbox_search.update_style_sheet(style_sheet) self.setStyleSheet(style_sheet) if extra_dialogs: cancel_dialog = extra_dialogs.get('cancel_dialog', None) apply_actions_dialog = extra_dialogs.get('apply_actions_dialog', None) message_box_error = extra_dialogs.get('message_box_error', None) if cancel_dialog: self.cancel_dialog = cancel_dialog if apply_actions_dialog: self.apply_actions_dialog = apply_actions_dialog if message_box_error: self.message_box_error = message_box_error # --- UI API # ------------------------------------------------------------------------- def filter_package(self, value): """ """ self.table.filter_status_changed(value) def show_channels_dialog(self): """ Show the channels dialog. """ button_channels = self.button_channels self.dlg = DialogChannels(self, channels=self._channels, active_channels=self._active_channels, conda_url=self.conda_url) self.dlg.update_style_sheet(style_sheet=self.style_sheet) button_channels.setDisabled(True) self.dlg.sig_channels_updated.connect(self.update_channels) self.dlg.rejected.connect(lambda: button_channels.setEnabled(True)) self.dlg.rejected.connect(button_channels.toggle) self.dlg.rejected.connect(button_channels.setFocus) self.dlg.accepted.connect(self.accept_channels_dialog) geo_tl = button_channels.geometry().topLeft() tl = button_channels.parentWidget().mapToGlobal(geo_tl) x = tl.x() + 2 y = tl.y() + button_channels.height() self.dlg.move(x, y) self.dlg.show() self.dlg.button_add.setFocus() def update_package_index(self): """ """ self.setup(check_updates=True) def search_package(self, text): """ """ self.table.search_string_changed(text) def apply_multiple_actions(self): """ """ logger.debug('') self.conda_errors = [] prefix = self.prefix if prefix == self.root_prefix: name = 'root' elif self.api.conda_environment_exists(prefix=prefix): name = osp.basename(prefix) else: name = prefix actions = self.table.get_actions() if actions is None: return self._multiple_process = deque() pip_actions = actions[C.PIP_PACKAGE] conda_actions = actions[C.CONDA_PACKAGE] pip_remove = pip_actions.get(C.ACTION_REMOVE, []) conda_remove = conda_actions.get(C.ACTION_REMOVE, []) conda_install = conda_actions.get(C.ACTION_INSTALL, []) conda_upgrade = conda_actions.get(C.ACTION_UPGRADE, []) conda_downgrade = conda_actions.get(C.ACTION_DOWNGRADE, []) message = '' template_1 = '<li><b>{0}={1}</b></li>' template_2 = '<li><b>{0}: {1} -> {2}</b></li>' if pip_remove: temp = [ template_1.format(i['name'], i['version_to']) for i in pip_remove ] message += ('The following pip packages will be removed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_remove: temp = [ template_1.format(i['name'], i['version_to']) for i in conda_remove ] message += ('<br>The following conda packages will be removed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_install: temp = [ template_1.format(i['name'], i['version_to']) for i in conda_install ] message += ('<br>The following conda packages will be installed: ' '<ul>' + ''.join(temp) + '</ul>') if conda_downgrade: temp = [ template_2.format(i['name'], i['version_from'], i['version_to']) for i in conda_downgrade ] message += ('<br>The following conda packages will be downgraded: ' '<ul>' + ''.join(temp) + '</ul>') if conda_upgrade: temp = [ template_2.format(i['name'], i['version_from'], i['version_to']) for i in conda_upgrade ] message += ('<br>The following conda packages will be upgraded: ' '<ul>' + ''.join(temp) + '</ul>') message += '<br>' if self.apply_actions_dialog: dlg = self.apply_actions_dialog(message, parent=self) dlg.update_style_sheet(style_sheet=self.style_sheet) reply = dlg.exec_() else: reply = QMessageBox.question(self, 'Proceed with the following actions?', message, buttons=QMessageBox.Ok | QMessageBox.Cancel) if reply: # Pip remove for pkg in pip_remove: status = (_('Removing pip package <b>') + pkg['name'] + '</b>' + _(' from <i>') + name + '</i>') pkgs = [pkg['name']] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.pip_remove(prefix=prefix, pkgs=pkgs) self._multiple_process.append([status, trigger()]) # Process conda actions if conda_remove: status = (_('Removing conda packages <b>') + '</b>' + _(' from <i>') + name + '</i>') pkgs = [i['name'] for i in conda_remove] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_remove(pkgs=pkgs, prefix=prefix) self._multiple_process.append([status, trigger()]) if conda_install: pkgs = [ '{0}={1}'.format(i['name'], i['version_to']) for i in conda_install ] status = (_('Installing conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install(prefix=prefix, pkgs=pkgs, channels=self. _active_channels, token=self.token) self._multiple_process.append([status, trigger()]) # Conda downgrade if conda_downgrade: status = (_('Downgrading conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') pkgs = [ '{0}={1}'.format(i['name'], i['version_to']) for i in conda_downgrade ] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install(prefix=prefix, pkgs=pkgs, channels=self. _active_channels, token=self.token) self._multiple_process.append([status, trigger()]) # Conda update if conda_upgrade: status = (_('Upgrading conda packages <b>') + '</b>' + _(' on <i>') + name + '</i>') pkgs = [ '{0}={1}'.format(i['name'], i['version_to']) for i in conda_upgrade ] def trigger(prefix=prefix, pkgs=pkgs): return lambda: self.api.conda_install(prefix=prefix, pkgs=pkgs, channels=self. _active_channels, token=self.token) self._multiple_process.append([status, trigger()]) self._run_multiple_actions() def clear_actions(self): """ """ self.table.clear_actions() def cancel_process(self): """ Allow user to cancel an ongoing process. """ logger.debug(str('process canceled by user.')) if self.busy: dlg = self.cancel_dialog() reply = dlg.exec_() if reply: self.update_status(hide=False, message='Process cancelled') self.api.conda_terminate() self.api.download_requests_terminate() self.api.conda_clear_lock() self.table.clear_actions() self.sig_process_cancelled.emit() else: QDialog.reject(self) def update_status(self, message=None, hide=True, progress=None, env=False): """ Update status bar, progress bar display and widget visibility message : str Message to display in status bar. hide : bool Enable/Disable widgets. progress : [int, int] Show status bar progress. [0, 0] means spinning statusbar. """ self.busy = hide for widget in self.widgets: widget.setDisabled(hide) self.table.verticalScrollBar().setValue(self._current_table_scroll) self.button_apply.setVisible(False) self.button_clear.setVisible(False) self.progress_bar.setVisible(hide) self.button_cancel.setVisible(hide) if message is not None: self.message = message if self.prefix == self.root_prefix: short_env = 'root' # elif self.api.environment_exists(prefix=self.prefix): # short_env = osp.basename(self.prefix) else: short_env = self.prefix if env: self.message = '{0} (<b>{1}</b>)'.format( self.message, short_env, ) self.status_bar.setText(self.message) if progress is not None: current_progress, max_progress = 0, 0 if progress[1]: max_progress = progress[1] if progress[0]: current_progress = progress[0] self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(max_progress) self.progress_bar.setValue(current_progress) else: self.progress_bar.setMinimum(0) self.progress_bar.setMaximum(0) # --- Conda helpers # ------------------------------------------------------------------------- def get_environment_prefix(self): """ Returns the active environment prefix. """ return self.prefix def get_environment_name(self): """ Returns the active environment name if it is located in the default conda environments directory, otherwise it returns the prefix. """ name = osp.basename(self.prefix) if not (name and self.api.environment_exists(name=name)): name = self.prefix return name def get_environments(self): """ Get a list of conda environments located in the default conda environments directory. """ return self.api.conda_get_envs() def get_prefix_envname(self, name): """ Returns the prefix for a given environment by name. """ return self.api.conda_get_prefix_envname(name) def get_package_versions(self, name): """ """ return self.table.source_model.get_package_versions(name) # --- Conda actions # ------------------------------------------------------------------------- def create_environment(self, name=None, prefix=None, packages=['python']): """ """ # If environment exists already? GUI should take care of this # BUT the api call should simply set that env as the env dic = {} dic['name'] = name dic['prefix'] = prefix dic['pkgs'] = packages dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_CREATE return self._run_conda_process(dic['action'], dic) def clone_environment(self, name=None, prefix=None, clone=None): dic = {} dic['name'] = name dic['prefix'] = prefix dic['clone'] = clone dic['pkgs'] = None dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_CLONE return self._run_conda_process(dic['action'], dic) def remove_environment(self, name=None, prefix=None): dic = {} dic['name'] = name dic['pkgs'] = None dic['dep'] = True # Not really needed but for the moment! dic['action'] = C.ACTION_REMOVE_ENV return self._run_conda_process(dic['action'], dic) # New api def show_login_dialog(self): pass def show_options_menu(self): pass
class OpticsAdjustSettings(SiriusDialog): """Auxiliar window to optics adjust settings.""" updateSettings = Signal(str, str) def __init__(self, tuneconfig_currname, chromconfig_currname, parent=None): """Initialize object.""" super().__init__(parent) self.setWindowTitle('Optics Adjust Settings') self.setObjectName('BOApp') self.tuneconfig_currname = tuneconfig_currname self.chromconfig_currname = chromconfig_currname self.conn_tuneparams = _ConfigDBClient( config_type='bo_tunecorr_params') self.conn_chromparams = _ConfigDBClient( config_type='bo_chromcorr_params') self._setupUi() def _setupUi(self): self.tune_settings = QWidget(self) self.tune_settings.setLayout(self._setupTuneSettings()) self.le_tuneconfig.setText(self.tuneconfig_currname) self.chrom_settings = QWidget(self) self.chrom_settings.setLayout(self._setupChromSettings()) self.le_chromconfig.setText(self.chromconfig_currname) self.bt_apply = QPushButton('Apply Settings', self) self.bt_apply.setStyleSheet("""min-width:8em; max-width:8em;""") self.bt_apply.clicked.connect(self._emitSettings) self.bt_apply.setAutoDefault(False) self.bt_apply.setDefault(False) hlay_apply = QHBoxLayout() hlay_apply.addItem( QSpacerItem(20, 60, QSzPlcy.Expanding, QSzPlcy.Ignored)) hlay_apply.addWidget(self.bt_apply) tabs = QTabWidget(self) tabs.addTab(self.tune_settings, 'Tune') tabs.addTab(self.chrom_settings, 'Chromaticity') lay = QVBoxLayout() lay.addWidget(tabs) lay.addLayout(hlay_apply) self.setLayout(lay) def _setupTuneSettings(self): l_tuneconfig = QLabel('<h3>Tune Variation Config</h3>', self) l_tuneconfig.setAlignment(Qt.AlignCenter) self.le_tuneconfig = _ConfigLineEdit(parent=self, config_type='bo_tunecorr_params') self.le_tuneconfig.textChanged.connect(self._showTuneConfigData) label_tunemat = QLabel('<h4>Matrix</h4>', self) label_tunemat.setAlignment(Qt.AlignCenter) self.table_tunemat = QTableWidget(self) self.table_tunemat.setObjectName('tunemat') self.table_tunemat.setStyleSheet(""" #tunemat{ background-color: #efebe7; min-width: 22.14em; min-height: 6em; max-height: 6em;}""") self.table_tunemat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_tunemat.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_tunemat.setRowCount(2) self.table_tunemat.setColumnCount(2) self.table_tunemat.setVerticalHeaderLabels([' X', ' Y']) self.table_tunemat.setHorizontalHeaderLabels(['QF', 'QD']) self.table_tunemat.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_tunemat.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_tunemat.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_tunemat.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_tunemat.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) label_nomKL = QLabel('<h4>Nominal KL</h4>') label_nomKL.setAlignment(Qt.AlignCenter) self.table_nomKL = QTableWidget(self) self.table_nomKL.setObjectName('nomKL') self.table_nomKL.setStyleSheet(""" #nomKL{ background-color: #efebe7; min-width: 22.14em; min-height: 4em; max-height: 4em;}""") self.table_nomKL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_nomKL.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_nomKL.setRowCount(1) self.table_nomKL.setColumnCount(2) self.table_nomKL.setVerticalHeaderLabels(['KL']) self.table_nomKL.setHorizontalHeaderLabels(['QF', 'QD']) self.table_nomKL.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_nomKL.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_nomKL.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomKL.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomKL.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) lay = QVBoxLayout() lay.addWidget(l_tuneconfig) lay.addWidget(self.le_tuneconfig) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) lay.addWidget(label_tunemat) lay.addWidget(self.table_tunemat) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) lay.addWidget(label_nomKL) lay.addWidget(self.table_nomKL) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Ignored, QSzPlcy.Expanding)) return lay def _setupChromSettings(self): l_chromconfig = QLabel('<h3>Chromaticity Variation Config</h3>', self) l_chromconfig.setAlignment(Qt.AlignCenter) self.le_chromconfig = _ConfigLineEdit( parent=self, config_type='bo_chromcorr_params') self.le_chromconfig.textChanged.connect(self._showChromConfigData) l_chrommat = QLabel('<h4>Matrix</h4>', self) l_chrommat.setAlignment(Qt.AlignCenter) self.table_chrommat = QTableWidget(self) self.table_chrommat.setObjectName('chrommat') self.table_chrommat.setStyleSheet(""" #chrommat{ background-color: #efebe7; min-width: 22.14em; min-height: 6em; max-height: 6em;}""") self.table_chrommat.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_chrommat.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_chrommat.setRowCount(2) self.table_chrommat.setColumnCount(2) self.table_chrommat.setVerticalHeaderLabels([' X', ' Y']) self.table_chrommat.setHorizontalHeaderLabels(['SF', 'SD']) self.table_chrommat.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_chrommat.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_chrommat.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_chrommat.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomSL = QLabel('<h4>Nominal SL</h4>') l_nomSL.setAlignment(Qt.AlignCenter) self.table_nomSL = QTableWidget(self) self.table_nomSL.setObjectName('nomSL') self.table_nomSL.setStyleSheet(""" #nomSL{ background-color: #efebe7; min-width: 22.14em; min-height: 4em; max-height: 4em;}""") self.table_nomSL.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_nomSL.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_nomSL.setRowCount(1) self.table_nomSL.setColumnCount(2) self.table_nomSL.setVerticalHeaderLabels(['SL']) self.table_nomSL.setHorizontalHeaderLabels(['SF', 'SD']) self.table_nomSL.horizontalHeader().setStyleSheet(""" min-height:1.55em; max-height:1.55em;""") self.table_nomSL.verticalHeader().setStyleSheet(""" min-width:1.55em; max-width:1.55em;""") self.table_nomSL.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.verticalHeader().setSectionResizeMode( QHeaderView.Stretch) self.table_nomSL.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Preferred) l_nomchrom = QLabel('<h4>Nominal Chrom</h4>') l_nomchrom.setAlignment(Qt.AlignCenter) self.label_nomchrom = QLabel() self.label_nomchrom.setAlignment(Qt.AlignCenter) lay = QVBoxLayout() lay.addWidget(l_chromconfig) lay.addWidget(self.le_chromconfig) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_chrommat) lay.addWidget(self.table_chrommat) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomSL) lay.addWidget(self.table_nomSL) lay.addItem(QSpacerItem(20, 10, QSzPlcy.Expanding, QSzPlcy.Expanding)) lay.addWidget(l_nomchrom) lay.addWidget(self.label_nomchrom) return lay def _showTuneConfigData(self): try: name = self.le_tuneconfig.text() config = self.conn_tuneparams.get_config_value(name=name) mat = config['matrix'] nomKL = config['nominal KLs'] except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) else: self.tuneconfig_currname = name self.table_tunemat.setItem(0, 0, QTableWidgetItem(str(mat[0][0]))) self.table_tunemat.setItem(0, 1, QTableWidgetItem(str(mat[0][1]))) self.table_tunemat.setItem(1, 0, QTableWidgetItem(str(mat[1][0]))) self.table_tunemat.setItem(1, 1, QTableWidgetItem(str(mat[1][1]))) self.table_tunemat.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(0, 1).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(1, 0).setFlags(Qt.ItemIsEnabled) self.table_tunemat.item(1, 1).setFlags(Qt.ItemIsEnabled) self.table_nomKL.setItem(0, 0, QTableWidgetItem(str(nomKL[0]))) self.table_nomKL.setItem(0, 1, QTableWidgetItem(str(nomKL[1]))) self.table_nomKL.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_nomKL.item(0, 1).setFlags(Qt.ItemIsEnabled) def _showChromConfigData(self): try: name = self.le_chromconfig.text() config = self.conn_chromparams.get_config_value(name=name) mat = config['matrix'] nomSL = config['nominal SLs'] nomChrom = config['nominal chrom'] except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) else: self.chromconfig_currname = name self.table_chrommat.setItem(0, 0, QTableWidgetItem(str(mat[0][0]))) self.table_chrommat.setItem(0, 1, QTableWidgetItem(str(mat[0][1]))) self.table_chrommat.setItem(1, 0, QTableWidgetItem(str(mat[1][0]))) self.table_chrommat.setItem(1, 1, QTableWidgetItem(str(mat[1][1]))) self.table_chrommat.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(0, 1).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(1, 0).setFlags(Qt.ItemIsEnabled) self.table_chrommat.item(1, 1).setFlags(Qt.ItemIsEnabled) self.table_nomSL.setItem(0, 0, QTableWidgetItem(str(nomSL[0]))) self.table_nomSL.setItem(0, 1, QTableWidgetItem(str(nomSL[1]))) self.table_nomSL.item(0, 0).setFlags(Qt.ItemIsEnabled) self.table_nomSL.item(0, 1).setFlags(Qt.ItemIsEnabled) self.label_nomchrom.setText(str(nomChrom)) def _emitSettings(self): tuneconfig_name = self.le_tuneconfig.text() chromconfig_name = self.le_chromconfig.text() self.updateSettings.emit(tuneconfig_name, chromconfig_name) self.close()
class ProjectDialog(QDialog): """Project creation dialog.""" sig_project_creation_requested = Signal(str, str, object) """ This signal is emitted to request the Projects plugin the creation of a project. Parameters ---------- project_path: str Location of project. project_type: str Type of project as defined by project types. project_packages: object Package to install. Currently not in use. """ def __init__(self, parent, project_types): """Project creation dialog.""" super(ProjectDialog, self).__init__(parent=parent) self.plugin = parent self._project_types = project_types self.project_data = {} self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.project_name = None self.location = get_home_dir() # Widgets projects_url = "http://docs.spyder-ide.org/current/panes/projects.html" self.description_label = QLabel( _("Select a new or existing directory to create a new Spyder " "project in it. To learn more about projects, take a look at " "our <a href=\"{0}\">documentation</a>.").format(projects_url)) self.description_label.setOpenExternalLinks(True) self.description_label.setWordWrap(True) self.groupbox = QGroupBox() self.radio_new_dir = QRadioButton(_("New directory")) self.radio_from_dir = QRadioButton(_("Existing directory")) self.label_project_name = QLabel(_('Project name')) self.label_location = QLabel(_('Location')) self.label_project_type = QLabel(_('Project type')) self.text_project_name = QLineEdit() self.text_location = QLineEdit(get_home_dir()) self.combo_project_type = QComboBox() self.label_information = QLabel("") self.label_information.hide() self.button_select_location = create_toolbutton( self, triggered=self.select_location, icon=ima.icon('DirOpenIcon'), tip=_("Select directory")) self.button_cancel = QPushButton(_('Cancel')) self.button_create = QPushButton(_('Create')) self.bbox = QDialogButtonBox(Qt.Horizontal) self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole) self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole) # Widget setup self.radio_new_dir.setChecked(True) self.text_location.setEnabled(True) self.text_location.setReadOnly(True) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) self.button_create.setEnabled(False) for (id_, name) in [(pt_id, pt.get_name()) for pt_id, pt in project_types.items()]: self.combo_project_type.addItem(name, id_) self.setWindowTitle(_('Create new project')) # Layouts layout_top = QHBoxLayout() layout_top.addWidget(self.radio_new_dir) layout_top.addSpacing(15) layout_top.addWidget(self.radio_from_dir) layout_top.addSpacing(200) self.groupbox.setLayout(layout_top) layout_grid = QGridLayout() layout_grid.addWidget(self.label_project_name, 0, 0) layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2) layout_grid.addWidget(self.label_location, 1, 0) layout_grid.addWidget(self.text_location, 1, 1) layout_grid.addWidget(self.button_select_location, 1, 2) layout_grid.addWidget(self.label_project_type, 2, 0) layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2) layout_grid.addWidget(self.label_information, 3, 0, 1, 3) layout = QVBoxLayout() layout.addWidget(self.description_label) layout.addSpacing(3) layout.addWidget(self.groupbox) layout.addSpacing(8) layout.addLayout(layout_grid) layout.addSpacing(8) layout.addWidget(self.bbox) layout.setSizeConstraint(layout.SetFixedSize) self.setLayout(layout) # Signals and slots self.button_create.clicked.connect(self.create_project) self.button_cancel.clicked.connect(self.close) self.radio_from_dir.clicked.connect(self.update_location) self.radio_new_dir.clicked.connect(self.update_location) self.text_project_name.textChanged.connect(self.update_location) def select_location(self): """Select directory.""" location = osp.normpath( getexistingdirectory( self, _("Select directory"), self.location, )) if location and location != '.': if is_writable(location): self.location = location self.text_project_name.setText(osp.basename(location)) self.update_location() def update_location(self, text=''): """Update text of location and validate it.""" msg = '' path_validation = False path = self.location name = self.text_project_name.text().strip() # Setup self.text_project_name.setEnabled(self.radio_new_dir.isChecked()) self.label_information.setText('') self.label_information.hide() if name and self.radio_new_dir.isChecked(): # Allow to create projects only on new directories. path = osp.join(self.location, name) path_validation = not osp.isdir(path) if not path_validation: msg = _("This directory already exists!") elif self.radio_from_dir.isChecked(): # Allow to create projects in current directories that are not # Spyder projects. path = self.location path_validation = not osp.isdir(osp.join(path, '.spyproject')) if not path_validation: msg = _("This directory is already a Spyder project!") # Set path in text_location self.text_location.setText(path) # Validate project name with the method from the currently selected # project. project_type_id = self.combo_project_type.currentData() validate_func = self._project_types[project_type_id].validate_name project_name_validation, project_msg = validate_func(path, name) if not project_name_validation: if msg: msg = msg + '\n\n' + project_msg else: msg = project_msg # Set message if msg: self.label_information.show() self.label_information.setText('\n' + msg) # Allow to create project if validation was successful validated = path_validation and project_name_validation self.button_create.setEnabled(validated) # Set default state of buttons according to validation # Fixes spyder-ide/spyder#16745 if validated: self.button_create.setDefault(True) self.button_create.setAutoDefault(True) else: self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) def create_project(self): """Create project.""" self.project_data = { "root_path": self.text_location.text(), "project_type": self.combo_project_type.currentData(), } self.sig_project_creation_requested.emit( self.text_location.text(), self.combo_project_type.currentData(), [], ) self.accept()
class InsertNormalizedConfig(SiriusDialog): """Auxiliary window to insert a new normalized config.""" insertConfig = Signal(float, str, dict) def __init__(self, parent, ramp_config): """Initialize object.""" super().__init__(parent) self.setObjectName('BOApp') self.ramp_config = ramp_config self.setWindowTitle('Insert Normalized Configuration') self._setupUi() def _setupUi(self): # selection widgets self.rb_interp = QRadioButton('Interpolate') self.rb_confsrv = QRadioButton('Take value from Config Server') self.rb_create = QRadioButton('Create from template') # data widget self.config_data = QWidget() glay_data = QGridLayout(self.config_data) self.le_interp_label = QLineEdit(self) # interpolate self.le_confsrv_name = _ConfigLineEdit( parent=self, config_type='bo_normalized') # from ConfigDB self.le_confsrv_name.textChanged.connect( self._handleInsertExistingConfig) self.le_confsrv_name.setVisible(False) self.le_nominal_label = QLineEdit(self) # from template self.le_nominal_label.setVisible(False) self.sb_time = QDoubleSpinBoxPlus(self) self.sb_time.setMaximum(490) self.sb_time.setDecimals(3) self.bt_insert = QPushButton('Insert', self) self.bt_insert.setAutoDefault(False) self.bt_insert.setDefault(False) self.bt_insert.clicked.connect(self._emitConfigData) self.bt_cancel = QPushButton('Cancel', self) self.bt_cancel.setAutoDefault(False) self.bt_cancel.setDefault(False) self.bt_cancel.clicked.connect(self.close) glay_data.addWidget(QLabel('Label: ', self), 0, 0) glay_data.addWidget(self.le_interp_label, 0, 1) glay_data.addWidget(self.le_confsrv_name, 0, 1) glay_data.addWidget(self.le_nominal_label, 0, 1) glay_data.addWidget(QLabel('Time [ms]: ', self), 1, 0) glay_data.addWidget(self.sb_time, 1, 1) glay_data.addWidget(self.bt_cancel, 2, 0) glay_data.addWidget(self.bt_insert, 2, 1) # connect visibility signals self.rb_interp.toggled.connect(self.le_interp_label.setVisible) self.rb_interp.setChecked(True) self.rb_confsrv.toggled.connect(self.le_confsrv_name.setVisible) self.rb_create.toggled.connect(self.le_nominal_label.setVisible) # layout lay = QVBoxLayout() lay.addWidget(QLabel('<h4>Insert a Normalized Configuration</h4>', self), alignment=Qt.AlignCenter) lay.addWidget(self.rb_interp) lay.addWidget(self.rb_confsrv) lay.addWidget(self.rb_create) lay.addStretch() lay.addWidget(self.config_data) self.setLayout(lay) @Slot(str) def _handleInsertExistingConfig(self, configname): try: self.norm_config = ramp.BoosterNormalized() self.norm_config.name = configname self.norm_config.load() energy = self.norm_config[ramp.BoosterRamp.PSNAME_DIPOLE_REF] time = self.ramp_config.ps_waveform_interp_time(energy) self.sb_time.setValue(time) except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) def _emitConfigData(self): time = self.sb_time.value() if self.le_interp_label.isVisible(): label = self.le_interp_label.text() psname2strength = dict() elif self.le_confsrv_name.isVisible(): label = self.le_confsrv_name.text() psname2strength = { _PVName(k).device_name: v for k, v, d in self.norm_config.value['pvs'] } elif self.le_nominal_label.isVisible(): label = self.le_nominal_label.text() psname2strength = \ self.ramp_config.ps_normalized_config_nominal_values self.insertConfig.emit(time, label, psname2strength) self.close()
class DeleteNormalizedConfig(SiriusDialog): """Auxiliary window to delete a normalized config.""" deleteConfig = Signal(float) def __init__(self, parent, table_map, selected_row): """Initialize object.""" super().__init__(parent) self.setObjectName('BOApp') self.setWindowTitle('Delete Normalized Configuration') self.table_map = table_map self.selected_row = selected_row self._setupUi() def _setupUi(self): self.sb_confignumber = QSpinBoxPlus(self) self.sb_confignumber.setMinimum(1) self.sb_confignumber.setMaximum(max(self.table_map['rows'].keys()) + 1) self.sb_confignumber.setStyleSheet("""max-width:5em;""") self.sb_confignumber.valueChanged.connect(self._searchConfigByIndex) self.bt_delete = QPushButton('Delete', self) self.bt_delete.setAutoDefault(False) self.bt_delete.setDefault(False) self.bt_delete.clicked.connect(self._emitConfigData) self.l_configid = QLabel('', self) self.l_configid.setSizePolicy(QSzPlcy.MinimumExpanding, QSzPlcy.Expanding) self.sb_confignumber.setValue(self.selected_row + 1) self._searchConfigByIndex(self.selected_row + 1) self.bt_cancel = QPushButton('Cancel', self) self.bt_cancel.setAutoDefault(False) self.bt_cancel.setDefault(False) self.bt_cancel.clicked.connect(self.close) glay = QGridLayout() glay.setVerticalSpacing(15) glay.setHorizontalSpacing(10) label = QLabel('<h4>Delete a Normalized Configuration</h4>', self) label.setAlignment(Qt.AlignCenter) glay.addWidget(label, 0, 0, 1, 2) glay.addWidget(self.sb_confignumber, 2, 0) glay.addWidget(self.l_configid, 2, 1) glay.addWidget(self.bt_cancel, 4, 0) glay.addWidget(self.bt_delete, 4, 1) self.setLayout(glay) @Slot(int) def _searchConfigByIndex(self, config_idx): label = self.table_map['rows'][config_idx - 1] self.l_configid.setText(str(label)) if label in ['Injection', 'Ejection']: self.bt_delete.setEnabled(False) else: self.bt_delete.setEnabled(True) def _emitConfigData(self): self.deleteConfig.emit(float(self.l_configid.text())) self.close()
def __init__(self, ispec, abs_sys, parent=None, llist=None, norm=True, vmnx=[-300., 300.] * u.km / u.s, outfil=None): """ spec : Filename or Spectrum1D abs_sys : AbsSystem Absorption system class Norm : bool, optional Normalized spectrum? """ from linetools.guis import spec_widgets as ltgs super(XAbsSysGui, self).__init__(parent) # Initialize self.abs_sys = abs_sys self.z = self.abs_sys.zabs abs_lines = abs_sys.list_of_abslines() self.vmnx = vmnx if outfil is None: self.outfil = 'tmp_abskin.json' warnings.warn( "Outfil not specified. Using {:s} as the default".format( self.outfil)) else: self.outfil = outfil self.norm = norm # Grab the pieces and tie together newfont = QtGui.QFont("Times", 10, QtGui.QFont.Bold) sys_label = QLabel('Name: \n {:s}'.format(abs_sys.name)) sys_label.setFont(newfont) self.vplt_widg = ltgs.VelPlotWidget(ispec, self.z, abs_lines=abs_lines, llist=llist, vmnx=self.vmnx, norm=self.norm) self.pltline_widg = ltgl.PlotLinesWidget( init_llist=self.vplt_widg.llist, init_z=self.z, edit_z=False) #self.pltline_widg.spec_widg = self.vplt_widg self.slines = ltgl.SelectedLinesWidget( self.vplt_widg.llist[self.vplt_widg.llist['List']], init_select=self.vplt_widg.llist['show_line'], plot_widget=self.vplt_widg) # Connections self.pltline_widg.llist_widget.currentItemChanged.connect( self.on_llist_change) #self.connect(self.pltline_widg.zbox, QtCore.SIGNAL('editingFinished ()'), self.setz) self.vplt_widg.canvas.mpl_connect('key_press_event', self.on_key) # Outfil wbtn = QPushButton(self) wbtn.setText('Write') wbtn.setAutoDefault(False) wbtn.clicked.connect(self.write_out) self.out_box = QLineEdit() self.out_box.setText(self.outfil) self.out_box.textChanged[str].connect(self.set_outfil) #self.connect(self.out_box, QtCore.SIGNAL('editingFinished ()'), self.set_outfil) #QtCore.pyqtRemoveInputHook() #pdb.set_trace() #QtCore.pyqtRestoreInputHook() # Quit buttons = QWidget() wqbtn = QPushButton(self) wqbtn.setText('Write+Quit') wqbtn.setAutoDefault(False) wqbtn.clicked.connect(self.write_quit) qbtn = QPushButton(self) qbtn.setText('Quit') qbtn.setAutoDefault(False) qbtn.clicked.connect(self.quit) # Sizes lines_widg = QWidget() lines_widg.setMaximumWidth(300) lines_widg.setMinimumWidth(200) # Layout vbox = QVBoxLayout() vbox.addWidget(sys_label) vbox.addWidget(self.pltline_widg) vbox.addWidget(self.slines) vbox.addWidget(wbtn) vbox.addWidget(self.out_box) # Write/Quit buttons hbox1 = QHBoxLayout() hbox1.addWidget(wqbtn) hbox1.addWidget(qbtn) buttons.setLayout(hbox1) # vbox.addWidget(buttons) lines_widg.setLayout(vbox) hbox = QHBoxLayout() hbox.addWidget(self.vplt_widg) hbox.addWidget(lines_widg) self.setLayout(hbox) # Initial draw self.vplt_widg.on_draw()
class DownloadBibleMp3Dialog(QDialog): def __init__(self, parent): super().__init__() self.bibles = { "BBE (British accent)": ("BBE", "otseng/UniqueBible_MP3_BBE_british", "british"), "KJV (American accent)": ("KJV", "otseng/UniqueBible_MP3_KJV", "default"), "KJV (American soft music)": ("KJV", "otseng/UniqueBible_MP3_KJV_soft_music", "soft-music"), "NHEB (Indian accent)": ("NHEB", "otseng/UniqueBible_MP3_NHEB_indian", "indian"), "WEB (American accent)": ("WEB", "otseng/UniqueBible_MP3_WEB", "default"), "CUV (Chinese)": ("CUV", "otseng/UniqueBible_MP3_CUV", "default"), "HHBD (Hindi)": ("HHBD", "otseng/UniqueBible_MP3_HHBD", "default"), "RVA (Spanish)": ("RVA", "otseng/UniqueBible_MP3_RVA", "default"), "TR (Modern Greek)": ("TR", "otseng/UniqueBible_MP3_TR", "modern"), } self.parent = parent self.setWindowTitle(config.thisTranslation["gitHubBibleMp3Files"]) self.setMinimumSize(150, 450) self.selectedRendition = None self.selectedText = None self.selectedRepo = None self.selectedDirectory = None self.settingBibles = False self.thread = None self.setupUI() def setupUI(self): mainLayout = QVBoxLayout() title = QLabel(config.thisTranslation["gitHubBibleMp3Files"]) mainLayout.addWidget(title) self.versionsLayout = QVBoxLayout() self.renditionsList = QListWidget() self.renditionsList.itemClicked.connect(self.selectItem) for rendition in self.bibles.keys(): self.renditionsList.addItem(rendition) self.renditionsList.setMaximumHeight(100) self.versionsLayout.addWidget(self.renditionsList) mainLayout.addLayout(self.versionsLayout) self.downloadTable = QTableView() self.downloadTable.setEnabled(False) self.downloadTable.setFocusPolicy(Qt.StrongFocus) self.downloadTable.setEditTriggers(QAbstractItemView.NoEditTriggers) self.downloadTable.setSortingEnabled(True) self.dataViewModel = QStandardItemModel(self.downloadTable) self.downloadTable.setModel(self.dataViewModel) mainLayout.addWidget(self.downloadTable) buttonsLayout = QHBoxLayout() selectAllButton = QPushButton(config.thisTranslation["selectAll"]) selectAllButton.setFocusPolicy(Qt.StrongFocus) selectAllButton.clicked.connect(self.selectAll) buttonsLayout.addWidget(selectAllButton) selectNoneButton = QPushButton(config.thisTranslation["selectNone"]) selectNoneButton.setFocusPolicy(Qt.StrongFocus) selectNoneButton.clicked.connect(self.selectNone) buttonsLayout.addWidget(selectNoneButton) otButton = QPushButton("1-39") otButton.setFocusPolicy(Qt.StrongFocus) otButton.clicked.connect(self.selectOT) buttonsLayout.addWidget(otButton) ntButton = QPushButton("40-66") ntButton.setFocusPolicy(Qt.StrongFocus) ntButton.clicked.connect(self.selectNT) buttonsLayout.addWidget(ntButton) # buttonsLayout.addStretch() mainLayout.addLayout(buttonsLayout) self.downloadButton = QPushButton(config.thisTranslation["download"]) self.downloadButton.setFocusPolicy(Qt.StrongFocus) self.downloadButton.setAutoDefault(True) self.downloadButton.setFocus() self.downloadButton.clicked.connect(self.download) mainLayout.addWidget(self.downloadButton) self.status = QLabel("") mainLayout.addWidget(self.status) buttonLayout = QHBoxLayout() self.closeButton = QPushButton(config.thisTranslation["close"]) self.closeButton.setFocusPolicy(Qt.StrongFocus) self.closeButton.clicked.connect(self.closeDialog) buttonLayout.addWidget(self.closeButton) mainLayout.addLayout(buttonLayout) self.setLayout(mainLayout) self.renditionsList.item(0).setSelected(True) bible = self.renditionsList.item(0).text() self.selectRendition(bible) self.downloadButton.setDefault(True) QTimer.singleShot(0, self.downloadButton.setFocus) def selectItem(self, item): self.selectRendition(item.text()) def selectRendition(self, rendition): from util.GithubUtil import GithubUtil self.selectedRendition = rendition self.downloadTable.setEnabled(True) self.selectedText, self.selectedRepo, self.selectedDirectory = self.bibles[ self.selectedRendition] self.github = GithubUtil(self.selectedRepo) self.repoData = self.github.getRepoData() self.settingBibles = True self.dataViewModel.clear() rowCount = 0 for file in self.repoData.keys(): if len(str(file)) > 3: engFullBookName = file[3:] else: engFullBookName = BibleBooks().eng[str(int(file))][1] item = QStandardItem(file[:3].strip()) folder = os.path.join("audio", "bibles", self.selectedText, self.selectedDirectory, file) folderWithName = os.path.join("audio", "bibles", self.selectedText, self.selectedDirectory, file + " " + engFullBookName) if os.path.exists(folder) or os.path.exists(folderWithName): item.setCheckable(False) item.setCheckState(Qt.Unchecked) item.setEnabled(False) else: item.setCheckable(True) item.setCheckState(Qt.Checked) item.setEnabled(True) self.dataViewModel.setItem(rowCount, 0, item) item = QStandardItem(engFullBookName) self.dataViewModel.setItem(rowCount, 1, item) if os.path.exists(folder) or os.path.exists(folderWithName): item = QStandardItem("Installed") self.dataViewModel.setItem(rowCount, 2, item) else: item = QStandardItem("") self.dataViewModel.setItem(rowCount, 2, item) rowCount += 1 self.dataViewModel.setHorizontalHeaderLabels([ config.thisTranslation["menu_book"], config.thisTranslation["name"], "" ]) self.downloadTable.setColumnWidth(0, 90) self.downloadTable.setColumnWidth(1, 125) self.downloadTable.setColumnWidth(2, 125) # self.downloadTable.resizeColumnsToContents() self.settingBibles = False def selectAll(self): for index in range(self.dataViewModel.rowCount()): item = self.dataViewModel.item(index) if item.isEnabled(): item.setCheckState(Qt.Checked) def selectNone(self): for index in range(self.dataViewModel.rowCount()): item = self.dataViewModel.item(index) item.setCheckState(Qt.Unchecked) def selectOT(self): for index in range(self.dataViewModel.rowCount()): item = self.dataViewModel.item(index) bookNum = int(item.text()) if bookNum <= 39: if item.isEnabled(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) else: item.setCheckState(Qt.Unchecked) def selectNT(self): for index in range(self.dataViewModel.rowCount()): item = self.dataViewModel.item(index) bookNum = int(item.text()) if bookNum >= 40: if item.isEnabled(): item.setCheckState(Qt.Checked) else: item.setCheckState(Qt.Unchecked) else: item.setCheckState(Qt.Unchecked) def download(self): self.downloadButton.setEnabled(False) self.setStatus(config.thisTranslation["message_installing"]) self.closeButton.setEnabled(False) folder = os.path.join("audio", "bibles") if not os.path.exists(folder): os.mkdir(folder) folder = os.path.join("audio", "bibles", self.selectedText) if not os.path.exists(folder): os.mkdir(folder) folder = os.path.join("audio", "bibles", self.selectedText, self.selectedDirectory) if not os.path.exists(folder): os.mkdir(folder) self.thread = QThread() self.worker = DownloadFromGitHub(self.github, self.repoData, self.dataViewModel, self.selectedText, self.selectedDirectory) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) self.worker.finished.connect(self.thread.quit) self.worker.finished.connect(self.worker.deleteLater) self.thread.finished.connect(self.worker.deleteLater) self.worker.finished.connect(self.finishedDownloading) self.worker.progress.connect(self.setStatus) self.thread.start() def finishedDownloading(self, count): self.selectRendition(self.selectedRendition) self.setStatus("") self.downloadButton.setEnabled(True) self.closeButton.setEnabled(True) if count > 0: self.parent.displayMessage( config.thisTranslation["message_installed"]) def setStatus(self, message): self.status.setText(message) QApplication.processEvents() def closeDialog(self): if self.thread: if self.thread.isRunning(): self.thread.quit() self.close()
class ProjectDialog(QDialog): """Project creation dialog.""" sig_project_creation_requested = Signal(str, str, object) """ This signal is emitted to request the Projects plugin the creation of a project. Parameters ---------- project_path: str Location of project. project_type: str Type of project as defined by project types. project_packages: object Package to install. Currently not in use. """ def __init__(self, parent, project_types): """Project creation dialog.""" super(ProjectDialog, self).__init__(parent=parent) self.plugin = parent self._project_types = project_types self.project_data = {} # Variables current_python_version = '.'.join([to_text_string(sys.version_info[0]), to_text_string(sys.version_info[1])]) python_versions = ['2.7', '3.4', '3.5'] if current_python_version not in python_versions: python_versions.append(current_python_version) python_versions = sorted(python_versions) self.project_name = None self.location = get_home_dir() # Widgets self.groupbox = QGroupBox() self.radio_new_dir = QRadioButton(_("New directory")) self.radio_from_dir = QRadioButton(_("Existing directory")) self.label_project_name = QLabel(_('Project name')) self.label_location = QLabel(_('Location')) self.label_project_type = QLabel(_('Project type')) self.label_python_version = QLabel(_('Python version')) self.text_project_name = QLineEdit() self.text_location = QLineEdit(get_home_dir()) self.combo_project_type = QComboBox() self.combo_python_version = QComboBox() self.label_information = QLabel("") self.button_select_location = QToolButton() self.button_cancel = QPushButton(_('Cancel')) self.button_create = QPushButton(_('Create')) self.bbox = QDialogButtonBox(Qt.Horizontal) self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole) self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole) # Widget setup self.combo_python_version.addItems(python_versions) self.radio_new_dir.setChecked(True) self.text_location.setEnabled(True) self.text_location.setReadOnly(True) self.button_select_location.setIcon(get_std_icon('DirOpenIcon')) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) self.button_create.setEnabled(False) for (id_, name) in [(pt_id, pt.get_name()) for pt_id, pt in project_types.items()]: self.combo_project_type.addItem(name, id_) self.combo_python_version.setCurrentIndex( python_versions.index(current_python_version)) self.setWindowTitle(_('Create new project')) self.setFixedWidth(500) self.label_python_version.setVisible(False) self.combo_python_version.setVisible(False) # Layouts layout_top = QHBoxLayout() layout_top.addWidget(self.radio_new_dir) layout_top.addWidget(self.radio_from_dir) layout_top.addStretch(1) self.groupbox.setLayout(layout_top) layout_grid = QGridLayout() layout_grid.addWidget(self.label_project_name, 0, 0) layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2) layout_grid.addWidget(self.label_location, 1, 0) layout_grid.addWidget(self.text_location, 1, 1) layout_grid.addWidget(self.button_select_location, 1, 2) layout_grid.addWidget(self.label_project_type, 2, 0) layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2) layout_grid.addWidget(self.label_python_version, 3, 0) layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2) layout_grid.addWidget(self.label_information, 4, 0, 1, 3) layout = QVBoxLayout() layout.addWidget(self.groupbox) layout.addSpacing(10) layout.addLayout(layout_grid) layout.addStretch() layout.addSpacing(20) layout.addWidget(self.bbox) self.setLayout(layout) # Signals and slots self.button_select_location.clicked.connect(self.select_location) self.button_create.clicked.connect(self.create_project) self.button_cancel.clicked.connect(self.close) self.radio_from_dir.clicked.connect(self.update_location) self.radio_new_dir.clicked.connect(self.update_location) self.text_project_name.textChanged.connect(self.update_location) def select_location(self): """Select directory.""" location = osp.normpath( getexistingdirectory( self, _("Select directory"), self.location, ) ) if location: if is_writable(location): self.location = location self.update_location() def update_location(self, text=''): """Update text of location.""" self.text_project_name.setEnabled(self.radio_new_dir.isChecked()) name = self.text_project_name.text().strip() if name and self.radio_new_dir.isChecked(): path = osp.join(self.location, name) self.button_create.setDisabled(os.path.isdir(path)) elif self.radio_from_dir.isChecked(): self.button_create.setEnabled(True) path = self.location else: self.button_create.setEnabled(False) path = self.location self.text_location.setText(path) # Validate name with the method from the currently selected project project_type_id = self.combo_project_type.currentData() validate_func = self._project_types[project_type_id].validate_name validated, msg = validate_func(path, name) msg = "" if validated else msg self.label_information.setText(msg) self.button_create.setEnabled(validated) def create_project(self): """Create project.""" self.project_data = { "root_path": self.text_location.text(), "project_type": self.combo_project_type.currentData(), } self.sig_project_creation_requested.emit( self.text_location.text(), self.combo_project_type.currentData(), [], ) self.accept()
def __init__(self, parent): super(KiteIntegrationInfo, self).__init__(parent) # Images images_layout = QHBoxLayout() icon_filename = 'kite_completions.png' image_path = get_image_path(icon_filename) image = QPixmap(image_path) image_label = QLabel() screen = QApplication.primaryScreen() image_label = QLabel() image_height = int(image.height() * self.ICON_SCALE_FACTOR) image_width = int(image.width() * self.ICON_SCALE_FACTOR) image = image.scaled(image_width, image_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) image_label.setPixmap(image) images_layout.addStretch() images_layout.addWidget(image_label) images_layout.addStretch() ilayout = QHBoxLayout() ilayout.addLayout(images_layout) # Label integration_label_title = QLabel( "Get better code completions in Spyder") integration_label_title.setStyleSheet( f"font-size: {self.TITLE_FONT_SIZE}") integration_label_title.setWordWrap(True) integration_label = QLabel( _("Now Spyder can use Kite to provide better code " "completions for key packages in the scientific Python " "Ecosystem. Install Kite for a better editor experience in " "Spyder. <br><br>Kite is free to use but is not open " "source. <a href=\"{kite_url}\">Learn more about Kite </a>"). format(kite_url=KITE_SPYDER_URL)) integration_label.setStyleSheet(f"font-size: {self.CONTENT_FONT_SIZE}") integration_label.setOpenExternalLinks(True) integration_label.setWordWrap(True) integration_label.setFixedWidth(360) label_layout = QVBoxLayout() label_layout.addWidget(integration_label_title) label_layout.addWidget(integration_label) # Buttons buttons_layout = QHBoxLayout() install_button = QPushButton(_('Install Kite')) install_button.setAutoDefault(False) install_button.setStyleSheet("background-color: #3775A9;" f"font-size: {self.BUTTONS_FONT_SIZE};" f"padding: {self.BUTTONS_PADDING}") dismiss_button = QPushButton(_('Dismiss')) dismiss_button.setAutoDefault(False) dismiss_button.setStyleSheet("background-color: #60798B;" f"font-size: {self.BUTTONS_FONT_SIZE};" f"padding: {self.BUTTONS_PADDING}") buttons_layout.addStretch() buttons_layout.addWidget(install_button) if not MAC: buttons_layout.addSpacing(10) buttons_layout.addWidget(dismiss_button) # Buttons with label vertical_layout = QVBoxLayout() if not MAC: vertical_layout.addStretch() vertical_layout.addLayout(label_layout) vertical_layout.addSpacing(20) vertical_layout.addLayout(buttons_layout) vertical_layout.addStretch() else: vertical_layout.addLayout(label_layout) vertical_layout.addLayout(buttons_layout) general_layout = QHBoxLayout() general_layout.addStretch() general_layout.addLayout(ilayout) general_layout.addSpacing(15) general_layout.addLayout(vertical_layout) general_layout.addStretch() self.setLayout(general_layout) # Signals install_button.clicked.connect(self.sig_install_button_clicked) dismiss_button.clicked.connect(self.sig_dismiss_button_clicked) if is_dark_interface(): self.setStyleSheet("background-color: #262E38") self.setContentsMargins(18, 40, 18, 40) if not MAC: self.setFixedSize(800, 350)
class TextEditor(BaseDialog): """Array Editor Dialog""" def __init__(self, text, title='', font=None, parent=None, readonly=False): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.text = None self.btn_save_and_close = None # Display text as unicode if it comes as bytes, so users see # its right representation if is_binary_string(text): self.is_binary = True text = to_text_string(text, 'utf8') else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.setReadOnly(readonly) self.edit.textChanged.connect(self.text_changed) self.edit.setPlainText(text) if font is None: font = get_font() self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(ima.icon('edit')) if title: try: unicode_title = to_text_string(title) except UnicodeEncodeError: unicode_title = u'' else: unicode_title = u'' self.setWindowTitle(_("Text editor") + \ u"%s" % (u" - " + unicode_title if unicode_title else u"")) @Slot() def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = to_binary_string(self.edit.toPlainText(), 'utf8') else: self.text = to_text_string(self.edit.toPlainText()) if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" try: to_text_string(value, 'utf8') return True except: return False
class ProjectDialog(QDialog): """Project creation dialog.""" # path, type, packages sig_project_creation_requested = Signal(object, object, object) def __init__(self, parent): """Project creation dialog.""" super(ProjectDialog, self).__init__(parent=parent) # Variables current_python_version = '.'.join([ to_text_string(sys.version_info[0]), to_text_string(sys.version_info[1]) ]) python_versions = ['2.7', '3.4', '3.5'] if current_python_version not in python_versions: python_versions.append(current_python_version) python_versions = sorted(python_versions) self.project_name = None self.location = get_home_dir() # Widgets self.groupbox = QGroupBox() self.radio_new_dir = QRadioButton(_("New directory")) self.radio_from_dir = QRadioButton(_("Existing directory")) self.label_project_name = QLabel(_('Project name')) self.label_location = QLabel(_('Location')) self.label_project_type = QLabel(_('Project type')) self.label_python_version = QLabel(_('Python version')) self.text_project_name = QLineEdit() self.text_location = QLineEdit(get_home_dir()) self.combo_project_type = QComboBox() self.combo_python_version = QComboBox() self.button_select_location = QToolButton() self.button_cancel = QPushButton(_('Cancel')) self.button_create = QPushButton(_('Create')) self.bbox = QDialogButtonBox(Qt.Horizontal) self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole) self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole) # Widget setup self.combo_python_version.addItems(python_versions) self.radio_new_dir.setChecked(True) self.text_location.setEnabled(True) self.text_location.setReadOnly(True) self.button_select_location.setIcon(get_std_icon('DirOpenIcon')) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) self.button_create.setEnabled(False) self.combo_project_type.addItems(self._get_project_types()) self.combo_python_version.setCurrentIndex( python_versions.index(current_python_version)) self.setWindowTitle(_('Create new project')) self.setFixedWidth(500) self.label_python_version.setVisible(False) self.combo_python_version.setVisible(False) # Layouts layout_top = QHBoxLayout() layout_top.addWidget(self.radio_new_dir) layout_top.addWidget(self.radio_from_dir) layout_top.addStretch(1) self.groupbox.setLayout(layout_top) layout_grid = QGridLayout() layout_grid.addWidget(self.label_project_name, 0, 0) layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2) layout_grid.addWidget(self.label_location, 1, 0) layout_grid.addWidget(self.text_location, 1, 1) layout_grid.addWidget(self.button_select_location, 1, 2) layout_grid.addWidget(self.label_project_type, 2, 0) layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2) layout_grid.addWidget(self.label_python_version, 3, 0) layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2) layout = QVBoxLayout() layout.addWidget(self.groupbox) layout.addSpacing(10) layout.addLayout(layout_grid) layout.addStretch() layout.addSpacing(20) layout.addWidget(self.bbox) self.setLayout(layout) # Signals and slots self.button_select_location.clicked.connect(self.select_location) self.button_create.clicked.connect(self.create_project) self.button_cancel.clicked.connect(self.close) self.radio_from_dir.clicked.connect(self.update_location) self.radio_new_dir.clicked.connect(self.update_location) self.text_project_name.textChanged.connect(self.update_location) def _get_project_types(self): """Get all available project types.""" project_types = get_available_project_types() projects = [] for project in project_types: projects.append(project.PROJECT_TYPE_NAME) return projects def select_location(self): """Select directory.""" location = getexistingdirectory(self, _("Select directory"), self.location) if location: if is_writable(location): self.location = location self.update_location() def update_location(self, text=''): """Update text of location.""" self.text_project_name.setEnabled(self.radio_new_dir.isChecked()) name = self.text_project_name.text().strip() if name and self.radio_new_dir.isChecked(): path = osp.join(self.location, name) self.button_create.setDisabled(os.path.isdir(path)) elif self.radio_from_dir.isChecked(): self.button_create.setEnabled(True) path = self.location else: self.button_create.setEnabled(False) path = self.location self.text_location.setText(path) def create_project(self): """Create project.""" packages = [ 'python={0}'.format(self.combo_python_version.currentText()) ] self.sig_project_creation_requested.emit( self.text_location.text(), self.combo_project_type.currentText(), packages) self.accept()
class ProjectDialog(QDialog): """Project creation dialog.""" # path, type, packages sig_project_creation_requested = Signal(object, object, object) def __init__(self, parent): """Project creation dialog.""" super(ProjectDialog, self).__init__(parent=parent) # Variables current_python_version = '.'.join([to_text_string(sys.version_info[0]), to_text_string(sys.version_info[1])]) python_versions = ['2.7', '3.4', '3.5'] if current_python_version not in python_versions: python_versions.append(current_python_version) python_versions = sorted(python_versions) self.project_name = None self.location = get_home_dir() # Widgets self.groupbox = QGroupBox() self.radio_new_dir = QRadioButton(_("New directory")) self.radio_from_dir = QRadioButton(_("Existing directory")) self.label_project_name = QLabel(_('Project name')) self.label_location = QLabel(_('Location')) self.label_project_type = QLabel(_('Project type')) self.label_python_version = QLabel(_('Python version')) self.text_project_name = QLineEdit() self.text_location = QLineEdit(get_home_dir()) self.combo_project_type = QComboBox() self.combo_python_version = QComboBox() self.button_select_location = QToolButton() self.button_cancel = QPushButton(_('Cancel')) self.button_create = QPushButton(_('Create')) self.bbox = QDialogButtonBox(Qt.Horizontal) self.bbox.addButton(self.button_cancel, QDialogButtonBox.ActionRole) self.bbox.addButton(self.button_create, QDialogButtonBox.ActionRole) # Widget setup self.combo_python_version.addItems(python_versions) self.radio_new_dir.setChecked(True) self.text_location.setEnabled(True) self.text_location.setReadOnly(True) self.button_select_location.setIcon(get_std_icon('DirOpenIcon')) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) self.button_create.setEnabled(False) self.combo_project_type.addItems(self._get_project_types()) self.combo_python_version.setCurrentIndex( python_versions.index(current_python_version)) self.setWindowTitle(_('Create new project')) self.setFixedWidth(500) self.label_python_version.setVisible(False) self.combo_python_version.setVisible(False) # Layouts layout_top = QHBoxLayout() layout_top.addWidget(self.radio_new_dir) layout_top.addWidget(self.radio_from_dir) layout_top.addStretch(1) self.groupbox.setLayout(layout_top) layout_grid = QGridLayout() layout_grid.addWidget(self.label_project_name, 0, 0) layout_grid.addWidget(self.text_project_name, 0, 1, 1, 2) layout_grid.addWidget(self.label_location, 1, 0) layout_grid.addWidget(self.text_location, 1, 1) layout_grid.addWidget(self.button_select_location, 1, 2) layout_grid.addWidget(self.label_project_type, 2, 0) layout_grid.addWidget(self.combo_project_type, 2, 1, 1, 2) layout_grid.addWidget(self.label_python_version, 3, 0) layout_grid.addWidget(self.combo_python_version, 3, 1, 1, 2) layout = QVBoxLayout() layout.addWidget(self.groupbox) layout.addSpacing(10) layout.addLayout(layout_grid) layout.addStretch() layout.addSpacing(20) layout.addWidget(self.bbox) self.setLayout(layout) # Signals and slots self.button_select_location.clicked.connect(self.select_location) self.button_create.clicked.connect(self.create_project) self.button_cancel.clicked.connect(self.close) self.radio_from_dir.clicked.connect(self.update_location) self.radio_new_dir.clicked.connect(self.update_location) self.text_project_name.textChanged.connect(self.update_location) def _get_project_types(self): """Get all available project types.""" project_types = get_available_project_types() projects = [] for project in project_types: projects.append(project.PROJECT_TYPE_NAME) return projects def select_location(self): """Select directory.""" location = osp.normpath(getexistingdirectory(self, _("Select directory"), self.location)) if location: if is_writable(location): self.location = location self.update_location() def update_location(self, text=''): """Update text of location.""" self.text_project_name.setEnabled(self.radio_new_dir.isChecked()) name = self.text_project_name.text().strip() if name and self.radio_new_dir.isChecked(): path = osp.join(self.location, name) self.button_create.setDisabled(os.path.isdir(path)) elif self.radio_from_dir.isChecked(): self.button_create.setEnabled(True) path = self.location else: self.button_create.setEnabled(False) path = self.location self.text_location.setText(path) def create_project(self): """Create project.""" packages = ['python={0}'.format(self.combo_python_version.currentText())] self.sig_project_creation_requested.emit( self.text_location.text(), self.combo_project_type.currentText(), packages) self.accept()
class DataFrameEditor(QDialog): """ Dialog for displaying and editing DataFrame and related objects. Signals ------- sig_option_changed(str, object): Raised if an option is changed. Arguments are name of option and its new value. """ sig_option_changed = Signal(str, object) def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.is_series = False self.layout = None def setup_and_check(self, data, title=''): """ Setup DataFrameEditor: return False if data is not supported, True otherwise. Supported types for data are DataFrame, Series and DatetimeIndex. """ self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - %s" % data.__class__.__name__ else: title = _("%s editor") % data.__class__.__name__ if isinstance(data, Series): self.is_series = True data = data.to_frame() elif isinstance(data, DatetimeIndex): data = DataFrame(data) self.setWindowTitle(title) self.resize(600, 500) self.dataModel = DataFrameModel(data, parent=self) self.dataModel.dataChanged.connect(self.save_and_close_enable) self.dataTable = DataFrameView(self, self.dataModel) self.layout.addWidget(self.dataTable) self.setLayout(self.layout) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) btn_layout = QHBoxLayout() btn = QPushButton(_("Format")) # disable format button for int type btn_layout.addWidget(btn) btn.clicked.connect(self.change_format) btn = QPushButton(_('Resize')) btn_layout.addWidget(btn) btn.clicked.connect(self.resize_to_contents) bgcolor = QCheckBox(_('Background color')) bgcolor.setChecked(self.dataModel.bgcolor_enabled) bgcolor.setEnabled(self.dataModel.bgcolor_enabled) bgcolor.stateChanged.connect(self.change_bgcolor_enable) btn_layout.addWidget(bgcolor) self.bgcolor_global = QCheckBox(_('Column min/max')) self.bgcolor_global.setChecked(self.dataModel.colum_avg_enabled) self.bgcolor_global.setEnabled(not self.is_series and self.dataModel.bgcolor_enabled) self.bgcolor_global.stateChanged.connect(self.dataModel.colum_avg) btn_layout.addWidget(self.bgcolor_global) btn_layout.addStretch() self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, top_left, bottom_right): """Handle the data change event to enable the save and close button.""" self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def change_bgcolor_enable(self, state): """ This is implementet so column min/max is only active when bgcolor is """ self.dataModel.bgcolor(state) self.bgcolor_global.setEnabled(not self.is_series and state > 0) def change_format(self): """ Ask user for display format for floats and use it. This function also checks whether the format is valid and emits `sig_option_changed`. """ format, valid = QInputDialog.getText(self, _('Format'), _("Float formatting"), QLineEdit.Normal, self.dataModel.get_format()) if valid: format = str(format) try: format % 1.1 except: msg = _("Format ({}) is incorrect").format(format) QMessageBox.critical(self, _("Error"), msg) return if not format.startswith('%'): msg = _("Format ({}) should start with '%'").format(format) QMessageBox.critical(self, _("Error"), msg) return self.dataModel.set_format(format) self.sig_option_changed.emit('dataframe_format', format) def get_value(self): """Return modified Dataframe -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute df = self.dataModel.get_data() if self.is_series: return df.iloc[:, 0] else: return df def resize_to_contents(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.dataTable.resizeColumnsToContents() self.dataModel.fetch_more(columns=True) self.dataTable.resizeColumnsToContents() QApplication.restoreOverrideCursor()
class DialogFindElements(QDialog): def __init__(self, parent=None): super().__init__(parent) self._dialog_data = {} self.setWindowTitle("Find Elements in Sample") # Check this flag after the dialog is exited with 'True' value # If this flag is True, then run element search, if False, # then simply save the changed parameter values. self.find_elements_requested = False self.validator = QDoubleValidator() self.le_e_calib_a0 = LineEditExtended() self.le_e_calib_a0.setValidator(self.validator) self.pb_e_calib_a0_default = QPushButton("Default") self.pb_e_calib_a0_default.setAutoDefault(False) self.le_e_calib_a1 = LineEditExtended() self.le_e_calib_a1.setValidator(self.validator) self.pb_e_calib_a1_default = QPushButton("Default") self.pb_e_calib_a1_default.setAutoDefault(False) self.le_e_calib_a2 = LineEditExtended() self.le_e_calib_a2.setValidator(self.validator) self.pb_e_calib_a2_default = QPushButton("Default") self.pb_e_calib_a2_default.setAutoDefault(False) self.group_energy_axis_calib = QGroupBox( "Polynomial Approximation of Energy Axis") set_tooltip( self.group_energy_axis_calib, "Parameters of polynomial approximation of <b>energy axis</b>. " "The values of the bins for photon energies are approximated " "using 2nd degree polynomial <b>E(n) = a0 + a1 * n + a2 * n^2</b>, " "where <b>n</b> is bin number (typically in the range 0..4096).", ) grid = QGridLayout() grid.addWidget(QLabel("Bias (a0):"), 0, 0) grid.addWidget(self.le_e_calib_a0, 0, 1) grid.addWidget(self.pb_e_calib_a0_default, 0, 2) grid.addWidget(QLabel("Linear (a1):"), 1, 0) grid.addWidget(self.le_e_calib_a1, 1, 1) grid.addWidget(self.pb_e_calib_a1_default, 1, 2) grid.addWidget(QLabel("Quadratic (a2):"), 2, 0) grid.addWidget(self.le_e_calib_a2, 2, 1) grid.addWidget(self.pb_e_calib_a2_default, 2, 2) self.group_energy_axis_calib.setLayout(grid) self.le_fwhm_b1 = LineEditExtended() self.le_fwhm_b1.setValidator(self.validator) self.pb_fwhm_b1_default = QPushButton("Default") self.pb_fwhm_b1_default.setAutoDefault(False) self.le_fwhm_b2 = LineEditExtended() self.le_fwhm_b2.setValidator(self.validator) self.pb_fwhm_b2_default = QPushButton("Default") self.pb_fwhm_b2_default.setAutoDefault(False) self.group_fwhm = QGroupBox("Peak FWHM Settings") set_tooltip( self.group_fwhm, "Parameters used to estimate <b>FWHM</b> of peaks based on energy: " "<b>b1</b> - 'offset', <b>b2</b> - 'fanoprime'", ) grid = QGridLayout() grid.addWidget(QLabel("Coefficient b1:"), 0, 0) grid.addWidget(self.le_fwhm_b1, 0, 1) grid.addWidget(self.pb_fwhm_b1_default, 0, 2) grid.addWidget(QLabel("Coefficient b2:"), 1, 0) grid.addWidget(self.le_fwhm_b2, 1, 1) grid.addWidget(self.pb_fwhm_b2_default, 1, 2) self.group_fwhm.setLayout(grid) self.le_incident_energy = LineEditExtended() self.le_incident_energy.setValidator(self.validator) set_tooltip(self.le_incident_energy, "<b>Incident energy</b> in keV.") self.pb_incident_energy_default = QPushButton("Default") self.pb_incident_energy_default.setAutoDefault(False) self.le_range_low = LineEditExtended() self.le_range_low.setValidator(self.validator) set_tooltip(self.le_range_low, "<b>Lower boundary</b> of the selected range in keV.") self.pb_range_low_default = QPushButton("Default") self.pb_range_low_default.setAutoDefault(False) self.le_range_high = LineEditExtended() self.le_range_high.setValidator(self.validator) set_tooltip(self.le_range_high, "<b>Upper boundary</b> of the selected range in keV.") self.pb_range_high_default = QPushButton("Default") self.pb_range_high_default.setAutoDefault(False) self.group_energy_range = QGroupBox( "Incident Energy and Selected Range") grid = QGridLayout() grid.addWidget(QLabel("Incident energy, keV"), 0, 0) grid.addWidget(self.le_incident_energy, 0, 1) grid.addWidget(self.pb_incident_energy_default, 0, 2) grid.addWidget(QLabel("Range (low), keV"), 1, 0) grid.addWidget(self.le_range_low, 1, 1) grid.addWidget(self.pb_range_low_default, 1, 2) grid.addWidget(QLabel("Range (high), keV"), 2, 0) grid.addWidget(self.le_range_high, 2, 1) grid.addWidget(self.pb_range_high_default, 2, 2) self.group_energy_range.setLayout(grid) self.pb_find_elements = QPushButton("Find &Elements") self.pb_find_elements.clicked.connect(self.pb_find_elements_clicked) self.pb_apply_settings = QPushButton("&Apply Settings") # 'Close' button box button_box = QDialogButtonBox(QDialogButtonBox.Cancel) button_box.addButton(self.pb_find_elements, QDialogButtonBox.YesRole) button_box.addButton(self.pb_apply_settings, QDialogButtonBox.AcceptRole) button_box.button(QDialogButtonBox.Cancel).setDefault(True) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) self.pb_cancel = button_box.button(QDialogButtonBox.Cancel) self.pb_cancel.setAutoDefault(False) self.pb_apply_settings.setAutoDefault(True) vbox = QVBoxLayout() vbox.addWidget(self.group_energy_axis_calib) vbox.addWidget(self.group_fwhm) vbox.addWidget(self.group_energy_range) vbox.addWidget(button_box) self.setLayout(vbox) self.le_e_calib_a0.editingFinished.connect( self.le_e_calib_a0_editing_finished) self.le_e_calib_a1.editingFinished.connect( self.le_e_calib_a1_editing_finished) self.le_e_calib_a2.editingFinished.connect( self.le_e_calib_a2_editing_finished) self.le_fwhm_b1.editingFinished.connect( self.le_fwhm_b1_editing_finished) self.le_fwhm_b2.editingFinished.connect( self.le_fwhm_b2_editing_finished) self.le_incident_energy.editingFinished.connect( self.le_incident_energy_editing_finished) self.le_range_low.editingFinished.connect( self.le_range_low_editing_finished) self.le_range_high.editingFinished.connect( self.le_range_high_editing_finished) self.le_e_calib_a0.focusOut.connect(self.le_e_calib_a0_focus_out) self.le_e_calib_a1.focusOut.connect(self.le_e_calib_a1_focus_out) self.le_e_calib_a2.focusOut.connect(self.le_e_calib_a2_focus_out) self.le_fwhm_b1.focusOut.connect(self.le_fwhm_b1_focus_out) self.le_fwhm_b2.focusOut.connect(self.le_fwhm_b2_focus_out) self.le_incident_energy.focusOut.connect( self.le_incident_energy_focus_out) self.le_range_low.focusOut.connect(self.le_range_low_focus_out) self.le_range_high.focusOut.connect(self.le_range_high_focus_out) self.le_e_calib_a0.textChanged.connect(self.le_e_calib_a0_text_changed) self.le_e_calib_a1.textChanged.connect(self.le_e_calib_a1_text_changed) self.le_e_calib_a2.textChanged.connect(self.le_e_calib_a2_text_changed) self.le_fwhm_b1.textChanged.connect(self.le_fwhm_b1_text_changed) self.le_fwhm_b2.textChanged.connect(self.le_fwhm_b2_text_changed) self.le_incident_energy.textChanged.connect( self.le_incident_energy_text_changed) self.le_range_low.textChanged.connect(self.le_range_low_text_changed) self.le_range_high.textChanged.connect(self.le_range_high_text_changed) self.pb_e_calib_a0_default.clicked.connect( self.pb_e_calib_a0_default_clicked) self.pb_e_calib_a1_default.clicked.connect( self.pb_e_calib_a1_default_clicked) self.pb_e_calib_a2_default.clicked.connect( self.pb_e_calib_a2_default_clicked) self.pb_fwhm_b1_default.clicked.connect( self.pb_fwhm_b1_default_clicked) self.pb_fwhm_b2_default.clicked.connect( self.pb_fwhm_b2_default_clicked) self.pb_incident_energy_default.clicked.connect( self.pb_incident_energy_default_clicked) self.pb_range_low_default.clicked.connect( self.pb_range_low_default_clicked) self.pb_range_high_default.clicked.connect( self.pb_range_high_default_clicked) def _format_float(self, value): return f"{value:.10g}" def set_dialog_data(self, dialog_data): self._dialog_data = copy.deepcopy(dialog_data) self.le_e_calib_a0.setText( self._format_float(self._dialog_data["e_offset"]["value"])) self.le_e_calib_a1.setText( self._format_float(self._dialog_data["e_linear"]["value"])) self.le_e_calib_a2.setText( self._format_float(self._dialog_data["e_quadratic"]["value"])) self.le_fwhm_b1.setText( self._format_float(self._dialog_data["fwhm_offset"]["value"])) self.le_fwhm_b2.setText( self._format_float(self._dialog_data["fwhm_fanoprime"]["value"])) self.le_incident_energy.setText( self._format_float( self._dialog_data["coherent_sct_energy"]["value"])) self.le_range_low.setText( self._format_float(self._dialog_data["energy_bound_low"]["value"])) self.le_range_high.setText( self._format_float( self._dialog_data["energy_bound_high"]["value"])) def get_dialog_data(self): return self._dialog_data def pb_find_elements_clicked(self): self.find_elements_requested = True def _read_le_value(self, line_edit, value_ref): value_ref["value"] = float(line_edit.text()) def le_e_calib_a0_editing_finished(self): self._read_le_value(self.le_e_calib_a0, self._dialog_data["e_offset"]) def le_e_calib_a1_editing_finished(self): self._read_le_value(self.le_e_calib_a1, self._dialog_data["e_linear"]) def le_e_calib_a2_editing_finished(self): self._read_le_value(self.le_e_calib_a2, self._dialog_data["e_quadratic"]) def le_fwhm_b1_editing_finished(self): self._read_le_value(self.le_fwhm_b1, self._dialog_data["fwhm_offset"]) def le_fwhm_b2_editing_finished(self): self._read_le_value(self.le_fwhm_b2, self._dialog_data["fwhm_fanoprime"]) def le_incident_energy_editing_finished(self): self._read_le_value(self.le_incident_energy, self._dialog_data["coherent_sct_energy"]) self._read_le_value(self.le_range_high, self._dialog_data["energy_bound_high"]) def le_range_low_editing_finished(self): self._read_le_value(self.le_range_low, self._dialog_data["energy_bound_low"]) def le_range_high_editing_finished(self): self._read_le_value(self.le_range_high, self._dialog_data["energy_bound_high"]) def _validate_as_float(self, text): return self.validator.validate(text, 0)[0] == QDoubleValidator.Acceptable def _validate_text(self, line_edit, text): line_edit.setValid(self._validate_as_float(text)) self._update_exit_buttons_states() def _update_exit_buttons_states(self): if (self.le_e_calib_a0.isValid() and self.le_e_calib_a1.isValid() and self.le_e_calib_a2.isValid() and self.le_fwhm_b1.isValid() and self.le_fwhm_b2.isValid() and self.le_incident_energy.isValid() and self.le_range_low.isValid() and self.le_range_high.isValid()): all_valid = True else: all_valid = False self.pb_find_elements.setEnabled(all_valid) self.pb_apply_settings.setEnabled(all_valid) def _range_high_update(self, text, margin=0.8): """Update the range 'high' limit based on incident energy""" v = float(text) + margin self.le_range_high.setText(self._format_float(v)) def le_e_calib_a0_text_changed(self, text): self._validate_text(self.le_e_calib_a0, text) def le_e_calib_a1_text_changed(self, text): self._validate_text(self.le_e_calib_a1, text) def le_e_calib_a2_text_changed(self, text): self._validate_text(self.le_e_calib_a2, text) def le_fwhm_b1_text_changed(self, text): self._validate_text(self.le_fwhm_b1, text) def le_fwhm_b2_text_changed(self, text): self._validate_text(self.le_fwhm_b2, text) def le_incident_energy_text_changed(self, text): if self._validate_as_float(text) and float(text) > 0: self.le_incident_energy.setValid(True) self._range_high_update(text) else: self.le_incident_energy.setValid(False) self._update_exit_buttons_states() def le_range_low_text_changed(self, text): text_valid = False if self._validate_as_float(text): v = float(text) if 0 <= v < self._dialog_data["energy_bound_high"]["value"]: text_valid = True self.le_range_low.setValid(text_valid) self.le_range_high.setValid(text_valid) self._update_exit_buttons_states() def le_range_high_text_changed(self, text): text_valid = False if self._validate_as_float(text): v = float(text) if v > self._dialog_data["energy_bound_low"]["value"]: text_valid = True self.le_range_high.setValid(text_valid) self.le_range_low.setValid(text_valid) self._update_exit_buttons_states() def _recover_last_valid_le_text(self, line_edit, last_value): if not self._validate_as_float(line_edit.text()): line_edit.setText(self._format_float(last_value["value"])) return False else: return True def le_e_calib_a0_focus_out(self): self._recover_last_valid_le_text(self.le_e_calib_a0, self._dialog_data["e_offset"]) def le_e_calib_a1_focus_out(self): self._recover_last_valid_le_text(self.le_e_calib_a1, self._dialog_data["e_linear"]) def le_e_calib_a2_focus_out(self): self._recover_last_valid_le_text(self.le_e_calib_a2, self._dialog_data["e_quadratic"]) def le_fwhm_b1_focus_out(self): self._recover_last_valid_le_text(self.le_fwhm_b1, self._dialog_data["fwhm_offset"]) def le_fwhm_b2_focus_out(self): self._recover_last_valid_le_text(self.le_fwhm_b2, self._dialog_data["fwhm_fanoprime"]) def le_incident_energy_focus_out(self): if not self._recover_last_valid_le_text( self.le_incident_energy, self._dialog_data["coherent_sct_energy"]): self.le_range_high.setText( self._format_float( self._dialog_data["energy_bound_high"]["value"])) def le_range_low_focus_out(self): self._recover_last_valid_le_text(self.le_range_low, self._dialog_data["energy_bound_low"]) def le_range_high_focus_out(self): self._recover_last_valid_le_text( self.le_range_high, self._dialog_data["energy_bound_high"]) def _reset_le_to_default(self, line_edit, data_ref): data_ref["value"] = data_ref["default"] line_edit.setText(self._format_float(data_ref["value"])) def pb_e_calib_a0_default_clicked(self): self._reset_le_to_default(self.le_e_calib_a0, self._dialog_data["e_offset"]) def pb_e_calib_a1_default_clicked(self): self._reset_le_to_default(self.le_e_calib_a1, self._dialog_data["e_linear"]) def pb_e_calib_a2_default_clicked(self): self._reset_le_to_default(self.le_e_calib_a2, self._dialog_data["e_quadratic"]) def pb_fwhm_b1_default_clicked(self): self._reset_le_to_default(self.le_fwhm_b1, self._dialog_data["fwhm_offset"]) def pb_fwhm_b2_default_clicked(self): self._reset_le_to_default(self.le_fwhm_b2, self._dialog_data["fwhm_fanoprime"]) def pb_incident_energy_default_clicked(self): self._reset_le_to_default(self.le_incident_energy, self._dialog_data["coherent_sct_energy"]) def pb_range_low_default_clicked(self): self._reset_le_to_default(self.le_range_low, self._dialog_data["energy_bound_low"]) def pb_range_high_default_clicked(self): self._reset_le_to_default(self.le_range_high, self._dialog_data["energy_bound_high"])
class ObjectExplorer(BaseDialog, SpyderConfigurationAccessor): """Object explorer main widget window.""" CONF_SECTION = 'variable_explorer' def __init__(self, obj, name='', expanded=False, resize_to_contents=True, parent=None, attribute_columns=DEFAULT_ATTR_COLS, attribute_details=DEFAULT_ATTR_DETAILS, readonly=None, reset=False): """ Constructor :param name: name of the object as it will appear in the root node :param expanded: show the first visible root element expanded :param resize_to_contents: resize columns to contents ignoring width of the attributes :param obj: any Python object or variable :param attribute_columns: list of AttributeColumn objects that define which columns are present in the table and their defaults :param attribute_details: list of AttributeDetails objects that define which attributes can be selected in the details pane. :param reset: If true the persistent settings, such as column widths, are reset. """ QDialog.__init__(self, parent=parent) self.setAttribute(Qt.WA_DeleteOnClose) # Options show_callable_attributes = self.get_conf('show_callable_attributes') show_special_attributes = self.get_conf('show_special_attributes') # Model self._attr_cols = attribute_columns self._attr_details = attribute_details self.readonly = readonly self.btn_save_and_close = None self.btn_close = None self._tree_model = TreeModel(obj, obj_name=name, attr_cols=self._attr_cols) self._proxy_tree_model = TreeProxyModel( show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes) self._proxy_tree_model.setSourceModel(self._tree_model) # self._proxy_tree_model.setSortRole(RegistryTableModel.SORT_ROLE) self._proxy_tree_model.setDynamicSortFilter(True) # self._proxy_tree_model.setSortCaseSensitivity(Qt.CaseInsensitive) # Tree widget self.obj_tree = ToggleColumnTreeView() self.obj_tree.setAlternatingRowColors(True) self.obj_tree.setModel(self._proxy_tree_model) self.obj_tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.obj_tree.setUniformRowHeights(True) self.obj_tree.add_header_context_menu() # Views self._setup_actions() self._setup_menu(show_callable_attributes=show_callable_attributes, show_special_attributes=show_special_attributes) self._setup_views() if name: name = "{} -".format(name) self.setWindowTitle("{} {}".format(name, EDITOR_NAME)) self.setWindowFlags(Qt.Window) self._resize_to_contents = resize_to_contents self._readViewSettings(reset=reset) # Update views with model self.toggle_show_special_attribute_action.setChecked( show_special_attributes) self.toggle_show_callable_action.setChecked(show_callable_attributes) # Select first row so that a hidden root node will not be selected. first_row_index = self._proxy_tree_model.firstItemIndex() self.obj_tree.setCurrentIndex(first_row_index) if self._tree_model.inspectedNodeIsVisible or expanded: self.obj_tree.expand(first_row_index) def get_value(self): """Get editor current object state.""" return self._tree_model.inspectedItem.obj def _make_show_column_function(self, column_idx): """Creates a function that shows or hides a column.""" show_column = lambda checked: self.obj_tree.setColumnHidden( column_idx, not checked) return show_column def _setup_actions(self): """Creates the main window actions.""" # Show/hide callable objects self.toggle_show_callable_action = QAction( _("Show callable attributes"), self, checkable=True, shortcut=QKeySequence("Alt+C"), statusTip=_("Shows/hides attributes that are callable " "(functions, methods, etc)")) self.toggle_show_callable_action.toggled.connect( self._proxy_tree_model.setShowCallables) self.toggle_show_callable_action.toggled.connect( self.obj_tree.resize_columns_to_contents) # Show/hide special attributes self.toggle_show_special_attribute_action = QAction( _("Show __special__ attributes"), self, checkable=True, shortcut=QKeySequence("Alt+S"), statusTip=_("Shows or hides __special__ attributes")) self.toggle_show_special_attribute_action.toggled.connect( self._proxy_tree_model.setShowSpecialAttributes) self.toggle_show_special_attribute_action.toggled.connect( self.obj_tree.resize_columns_to_contents) def _setup_menu(self, show_callable_attributes=False, show_special_attributes=False): """Sets up the main menu.""" self.tools_layout = QHBoxLayout() callable_attributes = create_toolbutton( self, text=_("Show callable attributes"), icon=ima.icon("class"), toggled=self._toggle_show_callable_attributes_action) callable_attributes.setCheckable(True) callable_attributes.setChecked(show_callable_attributes) self.tools_layout.addWidget(callable_attributes) special_attributes = create_toolbutton( self, text=_("Show __special__ attributes"), icon=ima.icon("private2"), toggled=self._toggle_show_special_attributes_action) special_attributes.setCheckable(True) special_attributes.setChecked(show_special_attributes) self.tools_layout.addWidget(special_attributes) self.tools_layout.addStretch() self.options_button = create_toolbutton(self, text=_('Options'), icon=ima.icon('tooloptions')) self.options_button.setPopupMode(QToolButton.InstantPopup) self.show_cols_submenu = QMenu(self) self.options_button.setMenu(self.show_cols_submenu) # Don't show menu arrow and remove padding if is_dark_interface(): self.options_button.setStyleSheet( ("QToolButton::menu-indicator{image: none;}\n" "QToolButton{padding: 3px;}")) else: self.options_button.setStyleSheet( "QToolButton::menu-indicator{image: none;}") self.tools_layout.addWidget(self.options_button) @Slot() def _toggle_show_callable_attributes_action(self): """Toggle show callable atributes action.""" action_checked = not self.toggle_show_callable_action.isChecked() self.toggle_show_callable_action.setChecked(action_checked) self.set_conf('show_callable_attributes', action_checked) @Slot() def _toggle_show_special_attributes_action(self): """Toggle show special attributes action.""" action_checked = ( not self.toggle_show_special_attribute_action.isChecked()) self.toggle_show_special_attribute_action.setChecked(action_checked) self.set_conf('show_special_attributes', action_checked) def _setup_views(self): """Creates the UI widgets.""" self.central_splitter = QSplitter(self, orientation=Qt.Vertical) layout = create_plugin_layout(self.tools_layout, self.central_splitter) self.setLayout(layout) # Stretch last column? # It doesn't play nice when columns are hidden and then shown again. obj_tree_header = self.obj_tree.header() obj_tree_header.setSectionsMovable(True) obj_tree_header.setStretchLastSection(False) add_actions(self.show_cols_submenu, self.obj_tree.toggle_column_actions_group.actions()) self.central_splitter.addWidget(self.obj_tree) # Bottom pane bottom_pane_widget = QWidget() bottom_layout = QHBoxLayout() bottom_layout.setSpacing(0) bottom_layout.setContentsMargins(5, 5, 5, 5) # left top right bottom bottom_pane_widget.setLayout(bottom_layout) self.central_splitter.addWidget(bottom_pane_widget) group_box = QGroupBox(_("Details")) bottom_layout.addWidget(group_box) v_group_layout = QVBoxLayout() h_group_layout = QHBoxLayout() h_group_layout.setContentsMargins(2, 2, 2, 2) # left top right bottom group_box.setLayout(v_group_layout) v_group_layout.addLayout(h_group_layout) # Radio buttons radio_widget = QWidget() radio_layout = QVBoxLayout() radio_layout.setContentsMargins(0, 0, 0, 0) # left top right bottom radio_widget.setLayout(radio_layout) self.button_group = QButtonGroup(self) for button_id, attr_detail in enumerate(self._attr_details): radio_button = QRadioButton(attr_detail.name) radio_layout.addWidget(radio_button) self.button_group.addButton(radio_button, button_id) self.button_group.buttonClicked[int].connect( self._change_details_field) self.button_group.button(0).setChecked(True) radio_layout.addStretch(1) h_group_layout.addWidget(radio_widget) # Editor widget self.editor = SimpleCodeEditor(self) self.editor.setReadOnly(True) h_group_layout.addWidget(self.editor) # Save and close buttons btn_layout = QHBoxLayout() btn_layout.addStretch() if not self.readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) v_group_layout.addLayout(btn_layout) # Splitter parameters self.central_splitter.setCollapsible(0, False) self.central_splitter.setCollapsible(1, True) self.central_splitter.setSizes([500, 320]) # Connect signals # Keep a temporary reference of the selection_model to prevent # segfault in PySide. # See http://permalink.gmane.org/gmane.comp.lib.qt.pyside.devel/222 selection_model = self.obj_tree.selectionModel() selection_model.currentChanged.connect(self._update_details) # Check if the values of the model have been changed self._proxy_tree_model.sig_setting_data.connect( self.save_and_close_enable) self._proxy_tree_model.sig_update_details.connect( self._update_details_for_item) # End of setup_methods def _readViewSettings(self, reset=False): """ Reads the persistent program settings. :param reset: If True, the program resets to its default settings. """ pos = QPoint(20, 20) window_size = QSize(825, 650) details_button_idx = 0 header = self.obj_tree.header() header_restored = False if reset: logger.debug("Resetting persistent view settings") else: pos = pos window_size = window_size details_button_idx = details_button_idx # splitter_state = settings.value("central_splitter/state") splitter_state = None if splitter_state: self.central_splitter.restoreState(splitter_state) # header_restored = self.obj_tree.read_view_settings( # 'table/header_state', # settings, reset) header_restored = False if not header_restored: column_sizes = [col.width for col in self._attr_cols] column_visible = [col.col_visible for col in self._attr_cols] for idx, size in enumerate(column_sizes): if not self._resize_to_contents and size > 0: # Just in case header.resizeSection(idx, size) else: header.resizeSections(QHeaderView.ResizeToContents) break for idx, visible in enumerate(column_visible): elem = self.obj_tree.toggle_column_actions_group.actions()[idx] elem.setChecked(visible) self.resize(window_size) button = self.button_group.button(details_button_idx) if button is not None: button.setChecked(True) @Slot() def save_and_close_enable(self): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) @Slot(QModelIndex, QModelIndex) def _update_details(self, current_index, _previous_index): """Shows the object details in the editor given an index.""" tree_item = self._proxy_tree_model.treeItem(current_index) self._update_details_for_item(tree_item) def _change_details_field(self, _button_id=None): """Changes the field that is displayed in the details pane.""" # logger.debug("_change_details_field: {}".format(_button_id)) current_index = self.obj_tree.selectionModel().currentIndex() tree_item = self._proxy_tree_model.treeItem(current_index) self._update_details_for_item(tree_item) @Slot(TreeItem) def _update_details_for_item(self, tree_item): """Shows the object details in the editor given an tree_item.""" try: # obj = tree_item.obj button_id = self.button_group.checkedId() assert button_id >= 0, ("No radio button selected. " "Please report this bug.") attr_details = self._attr_details[button_id] data = attr_details.data_fn(tree_item) self.editor.setPlainText(data) self.editor.setWordWrapMode(attr_details.line_wrap) self.editor.setup_editor( font=get_font(font_size_delta=DEFAULT_SMALL_DELTA), show_blanks=False, color_scheme=CONF.get('appearance', 'selected'), scroll_past_end=False, ) self.editor.set_text(data) if attr_details.name == 'Source code': self.editor.set_language('Python') else: self.editor.set_language('Rst') except Exception as ex: self.editor.setStyleSheet("color: red;") stack_trace = traceback.format_exc() self.editor.setPlainText("{}\n\n{}".format(ex, stack_trace)) self.editor.setWordWrapMode( QTextOption.WrapAtWordBoundaryOrAnywhere) @classmethod def create_explorer(cls, *args, **kwargs): """ Creates and shows and ObjectExplorer window. The *args and **kwargs will be passed to the ObjectExplorer constructor A (class attribute) reference to the browser window is kept to prevent it from being garbage-collected. """ object_explorer = cls(*args, **kwargs) object_explorer.exec_() return object_explorer
def _setup_ll_list_wid(self): ll_list_layout = QGridLayout(self.ll_list_wid) ll_list_layout.setHorizontalSpacing(20) ll_list_layout.setVerticalSpacing(20) but = QPushButton('Open LL Triggers', self) but.setAutoDefault(False) but.setDefault(False) obj_names = HLTimeSearch.get_ll_trigger_names(self.device.device_name) icon = qta.icon('mdi.timer', color=get_appropriate_color(self.device.sec)) Window = create_window_from_widget(LLTriggers, title=self.device.device_name + ': LL Triggers', icon=icon) connect_window(but, Window, self, prefix=self.prefix, hltrigger=self.device.device_name, obj_names=obj_names) ll_list_layout.addWidget(but, 0, 0, 1, 2) init_channel = self.get_pvname('LowLvlLock-Sel') sp = PyDMStateButton(self, init_channel=init_channel) init_channel = self.get_pvname('LowLvlLock-Sts') rb = PyDMLed(self, init_channel=init_channel) gb = self._create_small_group('Lock Low Level', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 1, 0) init_channel = self.get_pvname('State-Sel') sp = PyDMStateButton(self, init_channel=init_channel) init_channel = self.get_pvname('State-Sts') rb = PyDMLed(self, init_channel=init_channel) gb = self._create_small_group('Enabled', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 1, 1) init_channel = self.get_pvname('Polarity-Sel') sp = SiriusEnumComboBox(self, init_channel=init_channel) init_channel = self.get_pvname('Polarity-Sts') rb = PyDMLabel(self, init_channel=init_channel) gb = self._create_small_group('Polarity', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 2, 0) init_channel = self.get_pvname('Src-Sel') sp = SiriusEnumComboBox(self, init_channel=init_channel) init_channel = self.get_pvname('Src-Sts') rb = PyDMLabel(self, init_channel=init_channel) gb = self._create_small_group('Source', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 2, 1) init_channel = self.get_pvname('NrPulses-SP') sp = SiriusSpinbox(self, init_channel=init_channel) sp.showStepExponent = False init_channel = self.get_pvname('NrPulses-RB') rb = PyDMLabel(self, init_channel=init_channel) gb = self._create_small_group('Nr Pulses', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 3, 0) init_channel = self.get_pvname('Duration-SP') sp = SiriusSpinbox(self, init_channel=init_channel) sp.showStepExponent = False init_channel = self.get_pvname('Duration-RB') rb = PyDMLabel(self, init_channel=init_channel) gb = self._create_small_group('Duration [us]', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 3, 1) init_channel = self.get_pvname('Delay-SP') sp = SiriusSpinbox(self, init_channel=init_channel) sp.showStepExponent = False init_channel = self.get_pvname('Delay-RB') rb = PyDMLabel(self, init_channel=init_channel) gbdel = self._create_small_group('[us]', self.ll_list_wid, (sp, rb)) init_channel = self.get_pvname('DelayRaw-SP') sp = SiriusSpinbox(self, init_channel=init_channel) sp.showStepExponent = False init_channel = self.get_pvname('DelayRaw-RB') rb = PyDMLabel(self, init_channel=init_channel) gbdelr = self._create_small_group('Raw', self.ll_list_wid, (sp, rb)) init_channel = self.get_pvname('TotalDelay-Mon') rb = PyDMLabel(self, init_channel=init_channel) gbtdel = self._create_small_group('[us]', self.ll_list_wid, (rb, )) init_channel = self.get_pvname('TotalDelayRaw-Mon') rb = PyDMLabel(self, init_channel=init_channel) gbtdelr = self._create_small_group('Raw', self.ll_list_wid, (rb, )) widd = QWidget(self.ll_list_wid) widd.setLayout(QHBoxLayout()) widd.layout().addWidget(gbdel) widd.layout().addWidget(gbdelr) widt = QWidget(self.ll_list_wid) widt.setLayout(QHBoxLayout()) widt.layout().addWidget(gbtdel) widt.layout().addWidget(gbtdelr) tabdel = QTabWidget(self) tabdel.setObjectName(self.device.sec + 'Tab') tabdel.addTab(widd, 'Delay') tabdel.addTab(widt, 'Total Delay') if HLTimeSearch.has_delay_type(self.device.device_name): init_channel = self.get_pvname('RFDelayType-Sel') sp = SiriusEnumComboBox(self, init_channel=init_channel) init_channel = self.get_pvname('RFDelayType-Sts') rb = PyDMLabel(self, init_channel=init_channel) gb = self._create_small_group('Delay Type', self.ll_list_wid, (sp, rb)) ll_list_layout.addWidget(gb, 4, 0) ll_list_layout.addWidget(tabdel, 4, 1) else: ll_list_layout.addWidget(tabdel, 4, 0, 1, 2) gbdelta = self._create_deltadelay() ll_list_layout.addWidget(gbdelta, 0, 2, 5, 1)
class ArrayEditor(QDialog): """Array Editor Dialog""" def __init__(self, parent=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.data = None self.arraywidget = None self.stack = None self.layout = None self.btn_save_and_close = None self.btn_close = None # Values for 3d array editor self.dim_indexes = [{}, {}, {}] self.last_dim = 0 # Adjust this for changing the startup dimension def setup_and_check(self, data, title='', readonly=False, xlabels=None, ylabels=None): """ Setup ArrayEditor: return False if data is not supported, True otherwise """ self.data = data self.data.flags.writeable = True is_record_array = data.dtype.names is not None is_masked_array = isinstance(data, np.ma.MaskedArray) if data.ndim > 3: self.error( _("Arrays with more than 3 dimensions are not " "supported")) return False if xlabels is not None and len(xlabels) != self.data.shape[1]: self.error( _("The 'xlabels' argument length do no match array " "column number")) return False if ylabels is not None and len(ylabels) != self.data.shape[0]: self.error( _("The 'ylabels' argument length do no match array row " "number")) return False if not is_record_array: dtn = data.dtype.name if dtn not in SUPPORTED_FORMATS and not dtn.startswith('str') \ and not dtn.startswith('unicode'): arr = _("%s arrays") % data.dtype.name self.error(_("%s are currently not supported") % arr) return False self.layout = QGridLayout() self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - " + _("NumPy array") else: title = _("Array editor") if readonly: title += ' (' + _('read only') + ')' self.setWindowTitle(title) self.resize(600, 500) # Stack widget self.stack = QStackedWidget(self) if is_record_array: for name in data.dtype.names: self.stack.addWidget( ArrayEditorWidget(self, data[name], readonly, xlabels, ylabels)) elif is_masked_array: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.data, readonly, xlabels, ylabels)) self.stack.addWidget( ArrayEditorWidget(self, data.mask, readonly, xlabels, ylabels)) elif data.ndim == 3: pass else: self.stack.addWidget( ArrayEditorWidget(self, data, readonly, xlabels, ylabels)) self.arraywidget = self.stack.currentWidget() if self.arraywidget: self.arraywidget.model.dataChanged.connect( self.save_and_close_enable) self.stack.currentChanged.connect(self.current_widget_changed) self.layout.addWidget(self.stack, 1, 0) # Buttons configuration btn_layout = QHBoxLayout() if is_record_array or is_masked_array or data.ndim == 3: if is_record_array: btn_layout.addWidget(QLabel(_("Record array fields:"))) names = [] for name in data.dtype.names: field = data.dtype.fields[name] text = name if len(field) >= 3: title = field[2] if not is_text_string(title): title = repr(title) text += ' - ' + title names.append(text) else: names = [_('Masked data'), _('Data'), _('Mask')] if data.ndim == 3: # QSpinBox self.index_spin = QSpinBox(self, keyboardTracking=False) self.index_spin.valueChanged.connect(self.change_active_widget) # QComboBox names = [str(i) for i in range(3)] ra_combo = QComboBox(self) ra_combo.addItems(names) ra_combo.currentIndexChanged.connect(self.current_dim_changed) # Adding the widgets to layout label = QLabel(_("Axis:")) btn_layout.addWidget(label) btn_layout.addWidget(ra_combo) self.shape_label = QLabel() btn_layout.addWidget(self.shape_label) label = QLabel(_("Index:")) btn_layout.addWidget(label) btn_layout.addWidget(self.index_spin) self.slicing_label = QLabel() btn_layout.addWidget(self.slicing_label) # set the widget to display when launched self.current_dim_changed(self.last_dim) else: ra_combo = QComboBox(self) ra_combo.currentIndexChanged.connect( self.stack.setCurrentIndex) ra_combo.addItems(names) btn_layout.addWidget(ra_combo) if is_masked_array: label = QLabel( _("<u>Warning</u>: changes are applied separately")) label.setToolTip(_("For performance reasons, changes applied "\ "to masked array won't be reflected in "\ "array's data (and vice-versa).")) btn_layout.addWidget(label) btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout, 2, 0) self.setMinimumSize(400, 300) # Make the dialog act as a window self.setWindowFlags(Qt.Window) return True @Slot(QModelIndex, QModelIndex) def save_and_close_enable(self, left_top, bottom_right): """Handle the data change event to enable the save and close button.""" if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def current_widget_changed(self, index): self.arraywidget = self.stack.widget(index) self.arraywidget.model.dataChanged.connect(self.save_and_close_enable) def change_active_widget(self, index): """ This is implemented for handling negative values in index for 3d arrays, to give the same behavior as slicing """ string_index = [':'] * 3 string_index[self.last_dim] = '<font color=red>%i</font>' self.slicing_label.setText( (r"Slicing: [" + ", ".join(string_index) + "]") % index) if index < 0: data_index = self.data.shape[self.last_dim] + index else: data_index = index slice_index = [slice(None)] * 3 slice_index[self.last_dim] = data_index stack_index = self.dim_indexes[self.last_dim].get(data_index) if stack_index == None: stack_index = self.stack.count() try: self.stack.addWidget( ArrayEditorWidget(self, self.data[slice_index])) except IndexError: # Handle arrays of size 0 in one axis self.stack.addWidget(ArrayEditorWidget(self, self.data)) self.dim_indexes[self.last_dim][data_index] = stack_index self.stack.update() self.stack.setCurrentIndex(stack_index) def current_dim_changed(self, index): """ This change the active axis the array editor is plotting over in 3D """ self.last_dim = index string_size = ['%i'] * 3 string_size[index] = '<font color=red>%i</font>' self.shape_label.setText( ('Shape: (' + ', '.join(string_size) + ') ') % self.data.shape) if self.index_spin.value() != 0: self.index_spin.setValue(0) else: # this is done since if the value is currently 0 it does not emit # currentIndexChanged(int) self.change_active_widget(0) self.index_spin.setRange(-self.data.shape[index], self.data.shape[index] - 1) @Slot() def accept(self): """Reimplement Qt method""" for index in range(self.stack.count()): self.stack.widget(index).accept_changes() QDialog.accept(self) def get_value(self): """Return modified array -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.data def error(self, message): """An error occured, closing the dialog box""" QMessageBox.critical(self, _("Array editor"), message) self.setAttribute(Qt.WA_DeleteOnClose) self.reject() @Slot() def reject(self): """Reimplement Qt method""" if self.arraywidget is not None: for index in range(self.stack.count()): self.stack.widget(index).reject_changes() QDialog.reject(self)
class PreferencesDialog(DialogBase): sig_urls_updated = Signal(str, str) sig_show_application_environments = Signal(bool) def __init__(self, *args, **kwargs): super(PreferencesDialog, self).__init__(*args, **kwargs) self.api = AnacondaAPI() self.widgets_changed = set() self.widgets = [] # Widgets self.button_ok = QPushButton('Ok') self.button_cancel = ButtonCancel('Cancel') self.button_reset = QPushButton('Reset to defaults') self.row = 0 # Widget setup self.setWindowTitle("Preferences") # Layouts self.grid_layout = QGridLayout() buttons_layout = QHBoxLayout() buttons_layout.addWidget(self.button_reset) buttons_layout.addStretch() buttons_layout.addWidget(self.button_ok) buttons_layout.addWidget(self.button_cancel) main_layout = QVBoxLayout() main_layout.addLayout(self.grid_layout) main_layout.addSpacing(40) main_layout.addLayout(buttons_layout) self.setLayout(main_layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_reset.clicked.connect(self.reset_to_defaults) self.button_reset.clicked.connect( lambda: self.button_ok.setEnabled(True)) # Setup self.grid_layout.setSpacing(0) self.setup() self.button_ok.setDisabled(True) self.widgets[0].setFocus() self.button_ok.setDefault(True) self.button_ok.setAutoDefault(True) # --- Helpers # ------------------------------------------------------------------------- def get_option(self, option): return CONF.get('main', option, None) def set_option(self, option, value, default=False): CONF.set('main', option, value) def set_option_default(self, option): default = CONF.get_default('main', option) self.set_option(option, default) def create_widget(self, widget=None, label=None, option=None, hint=None, check=None): if hint: widget.setPlaceholderText(hint) config_value = self.get_option(option) widget.label = QLabel(label) widget.option = option widget.set_value(config_value) widget.label_information = QLabel() widget.label_information.setMinimumWidth(20) widget.label_information.setMaximumWidth(20) form_widget = QWidget() h_layout = QHBoxLayout() h_layout.addSpacing(4) h_layout.addWidget(widget.label_information, 0, Qt.AlignRight) h_layout.addWidget(widget, 0, Qt.AlignLeft) form_widget.setLayout(h_layout) if check: widget.check_value = lambda value: check(value) else: widget.check_value = lambda value: (True, '') self.widgets.append(widget) self.grid_layout.addWidget(widget.label, self.row, 0, Qt.AlignRight | Qt.AlignCenter) self.grid_layout.addWidget(form_widget, self.row, 1, Qt.AlignLeft | Qt.AlignCenter) self.row += 1 def create_textbox(self, label, option, hint=None, regex=None, check=None): widget = QLineEdit() widget.setAttribute(Qt.WA_MacShowFocusRect, False) widget.setMinimumWidth(250) if regex: regex_validator = QRegExpValidator(QRegExp(regex)) widget.setValidator(regex_validator) widget.get_value = lambda w=widget: w.text() widget.set_value = lambda value, w=widget: w.setText(value) widget.set_warning = lambda w=widget: w.setSelection(0, 1000) widget.textChanged.connect( lambda v=None, w=widget: self.options_changed(widget=w)) self.create_widget(widget=widget, option=option, label=label, hint=hint, check=check) def create_checkbox(self, label, option, hint=None): widget = QCheckBox() widget.get_value = lambda w=widget: bool(w.checkState()) widget.set_value = lambda value, w=widget: bool( w.setCheckState(Qt.Checked if value else Qt.Unchecked)) widget.stateChanged.connect( lambda v=None, w=widget: self.options_changed(widget=w)) self.create_widget(widget=widget, option=option, label=label, hint=hint) def options_changed(self, value=None, widget=None): config_value = self.get_option(widget.option) if config_value != widget.get_value(): self.widgets_changed.add(widget) else: if widget in self.widgets_changed: self.widgets_changed.remove(widget) self.button_ok.setDisabled(not bool(len(self.widgets_changed))) # --- API # ------------------------------------------------------------------------- def setup(self): self.create_textbox('Anaconda API domain', 'anaconda_api_url', check=self.is_valid_api) self.create_textbox('Conda domain', 'conda_url', check=self.is_valid_url) self.create_checkbox('Provide analytics', 'provide_analytics') self.create_checkbox("Hide quit dialog", 'hide_quit_dialog') # self.create_checkbox('Show application<br>environments', # 'show_application_environments') def warn(self, widget, text=None): """ """ label = widget.label_information if text: pixmap = QPixmap(WARNING_ICON) label.setPixmap( pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation)) label.setToolTip(str(text)) else: label.setPixmap(QPixmap()) label.setToolTip('') # --- Checkers # ------------------------------------------------------------------------- def is_valid_url(self, url): """ Chek if a given URL returns a 200 code. """ output = self.api.download_is_valid_url(url, non_blocking=False) error = '' if not output: error = 'Invalid url' return output, error def is_valid_api(self, url): """ Chek if a given URL is a valid anaconda api endpoint. """ output = self.api.download_is_valid_api_url(url, non_blocking=False) error = '' if not output: error = 'Invalid Anaconda API url.' return output, error def reset_to_defaults(self): """ """ # ASK! for widget in self.widgets: option = widget.option self.set_option_default(option) value = self.get_option(option) widget.set_value(value) def accept(self): """ Qt override. """ sig_updated = False for widget in self.widgets_changed: option = widget.option value = widget.get_value() check, error = widget.check_value(value) if check: self.set_option(option, value) self.warn(widget) else: self.button_ok.setDisabled(True) widget.set_warning() self.warn(widget, error) return if widget.option == 'conda_url': sig_updated = True if widget.option == 'anaconda_api_url': sig_updated = True if widget.option == 'show_application_environments': self.sig_show_application_environments.emit(value) for widget in self.widgets: if widget.option == 'anaconda_api_url': anaconda_api_url = widget.get_value() elif widget.option == 'conda_url': conda_url = widget.get_value() if sig_updated: self.sig_urls_updated.emit(anaconda_api_url, conda_url) super(PreferencesDialog, self).accept()