def add_plugin(self, plugin_descriptor): base_path = plugin_descriptor.attributes().get('plugin_path') menu_manager = self._plugin_menu_manager # create submenus for group in plugin_descriptor.groups(): label = group['label'] if menu_manager.contains_menu(label): submenu = menu_manager.get_menu(label) else: submenu = QMenu(label, menu_manager.menu) menu_action = submenu.menuAction() self._enrich_action(menu_action, group, base_path) menu_manager.add_item(submenu) menu_manager = MenuManager(submenu) # create action action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], menu_manager.menu) self._enrich_action(action, action_attributes, base_path) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) not_available = plugin_descriptor.attributes().get('not_available') if not_available: action.setEnabled(False) action.setStatusTip( self.tr('Plugin is not available: %s') % not_available) # add action to menu menu_manager.add_item(action)
def add_plugin(self, plugin_descriptor): base_path = plugin_descriptor.attributes().get('plugin_path') menu_manager = self._plugin_menu_manager # create submenus for group in plugin_descriptor.groups(): label = group['label'] if menu_manager.contains_menu(label): submenu = menu_manager.get_menu(label) else: submenu = QMenu(label, menu_manager.menu) menu_action = submenu.menuAction() self._enrich_action(menu_action, group, base_path) menu_manager.add_item(submenu) menu_manager = MenuManager(submenu) # create action action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], menu_manager.menu) self._enrich_action(action, action_attributes, base_path) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) not_available = plugin_descriptor.attributes().get('not_available') if not_available: action.setEnabled(False) action.setStatusTip(self.tr('Plugin is not available: %s') % not_available) # add action to menu menu_manager.add_item(action)
class PerspectiveManager(QObject): """Manager for perspectives associated with specific sets of `Settings`.""" perspective_changed_signal = Signal(basestring) save_settings_signal = Signal(Settings, Settings) restore_settings_signal = Signal(Settings, Settings) restore_settings_without_plugin_changes_signal = Signal(Settings, Settings) HIDDEN_PREFIX = '@' def __init__(self, settings, application_context): super(PerspectiveManager, self).__init__() self.setObjectName('PerspectiveManager') self._qtgui_path = application_context.qtgui_path self._settings_proxy = SettingsProxy(settings) self._global_settings = Settings(self._settings_proxy, 'global') self._perspective_settings = None self._create_perspective_dialog = None self._menu_manager = None self._perspective_mapper = None # get perspective list from settings self.perspectives = self._settings_proxy.value('', 'perspectives', []) if isinstance(self.perspectives, basestring): self.perspectives = [self.perspectives] self._current_perspective = None self._remove_action = None self._callback = None self._callback_args = [] if application_context.provide_app_dbus_interfaces: from .perspective_manager_dbus_interface import PerspectiveManagerDBusInterface self._dbus_server = PerspectiveManagerDBusInterface(self, application_context) def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def set_perspective(self, name, hide_and_without_plugin_changes=False): if name is None: name = self._settings_proxy.value('', 'current-perspective', 'Default') elif hide_and_without_plugin_changes: name = self.HIDDEN_PREFIX + name self.switch_perspective(name, save_before=not hide_and_without_plugin_changes, without_plugin_changes=hide_and_without_plugin_changes) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) def switch_perspective(self, name, settings_changed=True, save_before=True, without_plugin_changes=False): if save_before and self._global_settings is not None and self._perspective_settings is not None: self._callback = self._switch_perspective self._callback_args = [name, settings_changed, save_before] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._switch_perspective(name, settings_changed, save_before, without_plugin_changes) def _switch_perspective(self, name, settings_changed, save_before, without_plugin_changes=False): # convert from unicode name = str(name.replace('/', '__')) qDebug('PerspectiveManager.switch_perspective() switching to perspective "%s"' % name) if self._current_perspective is not None and self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, False) self._menu_manager.set_item_disabled(self._current_perspective, False) # create perspective if necessary if name not in self.perspectives: self._create_perspective(name, clone_perspective=False) # update current perspective self._current_perspective = name if self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, True) self._menu_manager.set_item_disabled(self._current_perspective, True) if not self._current_perspective.startswith(self.HIDDEN_PREFIX): self._settings_proxy.set_value('', 'current-perspective', self._current_perspective) self._perspective_settings = self._get_perspective_settings(self._current_perspective) # emit signals self.perspective_changed_signal.emit(self._current_perspective.lstrip(self.HIDDEN_PREFIX)) if settings_changed: if not without_plugin_changes: self.restore_settings_signal.emit(self._global_settings, self._perspective_settings) else: self.restore_settings_without_plugin_changes_signal.emit(self._global_settings, self._perspective_settings) def save_settings_completed(self): if self._callback is not None: callback = self._callback callback_args = self._callback_args self._callback = None self._callback_args = [] callback(*callback_args) def _get_perspective_settings(self, perspective_name): return Settings(self._settings_proxy, 'perspective/%s' % perspective_name) def _on_create_perspective(self): name = self._choose_new_perspective_name() if name is not None: clone_perspective = self._create_perspective_dialog.clone_checkbox.isChecked() self._create_perspective(name, clone_perspective) self.switch_perspective(name, settings_changed=not clone_perspective, save_before=False) def _choose_new_perspective_name(self, show_cloning=True): # input dialog for new perspective name if self._create_perspective_dialog is None: ui_file = os.path.join(self._qtgui_path, 'resource', 'perspective_create.ui') self._create_perspective_dialog = loadUi(ui_file) # custom validator preventing forward slashs class CustomValidator(QValidator): def __init__(self, parent=None): super(CustomValidator, self).__init__(parent) def fixup(self, value): value = value.replace('/', '') def validate(self, value, pos): if value.find('/') != -1: pos = value.find('/') return (QValidator.Invalid, value, pos) if value == '': return (QValidator.Intermediate, value, pos) return (QValidator.Acceptable, value, pos) self._create_perspective_dialog.perspective_name_edit.setValidator(CustomValidator()) # set default values self._create_perspective_dialog.perspective_name_edit.setText('') self._create_perspective_dialog.clone_checkbox.setChecked(True) self._create_perspective_dialog.clone_checkbox.setVisible(show_cloning) # show dialog and wait for it's return value return_value = self._create_perspective_dialog.exec_() if return_value == self._create_perspective_dialog.Rejected: return name = str(self._create_perspective_dialog.perspective_name_edit.text()).lstrip(self.HIDDEN_PREFIX) if name == '': QMessageBox.warning(self._menu_manager.menu, self.tr('Empty perspective name'), self.tr('The name of the perspective must be non-empty.')) return if name in self.perspectives: QMessageBox.warning(self._menu_manager.menu, self.tr('Duplicate perspective name'), self.tr('A perspective with the same name already exists.')) return return name def _create_perspective(self, name, clone_perspective=True): # convert from unicode name = str(name) if name.find('/') != -1: raise RuntimeError('PerspectiveManager._create_perspective() name must not contain forward slashs (/)') qDebug('PerspectiveManager._create_perspective(%s, %s)' % (name, clone_perspective)) # add to list of perspectives self.perspectives.append(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # save current settings if self._global_settings is not None and self._perspective_settings is not None: self._callback = self._create_perspective_continued self._callback_args = [name, clone_perspective] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._create_perspective_continued(name, clone_perspective) def _create_perspective_continued(self, name, clone_perspective): # clone settings if clone_perspective: new_settings = self._get_perspective_settings(name) keys = self._perspective_settings.all_keys() for key in keys: value = self._perspective_settings.value(key) new_settings.set_value(key, value) # add and switch to perspective if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True) def _on_remove_perspective(self): # input dialog to choose perspective to be removed names = list(self.perspectives) names.remove(self._current_perspective) name, return_value = QInputDialog.getItem(self._menu_manager.menu, self._menu_manager.tr('Remove perspective'), self._menu_manager.tr('Select the perspective'), names, 0, False) # convert from unicode name = str(name) if return_value == QInputDialog.Rejected: return self._remove_perspective(name) def _remove_perspective(self, name): if name not in self.perspectives: raise UserWarning('unknown perspective: %s' % name) qDebug('PerspectiveManager._remove_perspective(%s)' % str(name)) # remove from list of perspectives self.perspectives.remove(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # remove settings settings = self._get_perspective_settings(name) settings.remove('') # remove from menu self._menu_manager.remove_item(name) # disable remove-action if self._menu_manager.count_items() < 2: self._remove_action.setEnabled(False) def _on_import_perspective(self): file_name, _ = QFileDialog.getOpenFileName(self._menu_manager.menu, self.tr('Import perspective from file'), None, self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return perspective_name = os.path.basename(file_name) suffix = '.perspective' if perspective_name.endswith(suffix): perspective_name = perspective_name[:-len(suffix)] if perspective_name in self.perspectives: perspective_name = self._choose_new_perspective_name(False) if perspective_name is None: return self.import_perspective_from_file(file_name, perspective_name) def import_perspective_from_file(self, path, perspective_name): # create clean perspective if perspective_name in self.perspectives: self._remove_perspective(perspective_name) self._create_perspective(perspective_name, clone_perspective=False) # read perspective from file file_handle = open(path, 'r') #data = eval(file_handle.read()) data = json.loads(file_handle.read()) self._convert_values(data, self._import_value) new_settings = self._get_perspective_settings(perspective_name) self._set_dict_on_settings(data, new_settings) self.switch_perspective(perspective_name, settings_changed=True, save_before=True) def _set_dict_on_settings(self, data, settings): """Set dictionary key-value pairs on Settings instance.""" keys = data.get('keys', {}) for key in keys: settings.set_value(key, keys[key]) groups = data.get('groups', {}) for group in groups: sub = settings.get_settings(group) self._set_dict_on_settings(groups[group], sub) def _on_export_perspective(self): file_name, _ = QFileDialog.getSaveFileName(self._menu_manager.menu, self.tr('Export perspective to file'), self._current_perspective + '.perspective', self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return # trigger save of perspective before export self._callback = self._on_export_perspective_continued self._callback_args = [file_name] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) def _on_export_perspective_continued(self, file_name): # convert every value data = self._get_dict_from_settings(self._perspective_settings) self._convert_values(data, self._export_value) # write perspective data to file file_handle = open(file_name, 'w') file_handle.write(json.dumps(data, indent=2)) file_handle.close() def _get_dict_from_settings(self, settings): """Convert data of Settings instance to dictionary.""" keys = {} for key in settings.child_keys(): keys[str(key)] = settings.value(key) groups = {} for group in settings.child_groups(): sub = settings.get_settings(group) groups[str(group)] = self._get_dict_from_settings(sub) return {'keys': keys, 'groups': groups} def _convert_values(self, data, convert_function): keys = data.get('keys', {}) for key in keys: keys[key] = convert_function(keys[key]) groups = data.get('groups', {}) for group in groups: self._convert_values(groups[group], convert_function) def _import_value(self, value): import QtCore # @UnusedImport if value['type'] == 'repr': return eval(value['repr']) elif value['type'] == 'repr(QByteArray.hex)': return QByteArray.fromHex(eval(value['repr(QByteArray.hex)'])) raise RuntimeError('PerspectiveManager._import_value() unknown serialization type (%s)' % value['type']) def _export_value(self, value): data = {} if value.__class__.__name__ == 'QByteArray': hex_value = value.toHex() data['repr(QByteArray.hex)'] = self._strip_qt_binding_prefix(hex_value, repr(hex_value)) data['type'] = 'repr(QByteArray.hex)' # add pretty print for better readability characters = '' for i in range(1, value.size(), 2): character = value.at(i) # output all non-control characters if character >= ' ' and character <= '~': characters += character else: characters += ' ' data['pretty-print'] = characters else: data['repr'] = self._strip_qt_binding_prefix(value, repr(value)) data['type'] = 'repr' # verify that serialized data can be deserialized correctly reimported = self._import_value(data) if reimported != value: raise RuntimeError('PerspectiveManager._export_value() stored value can not be restored (%s)' % type(value)) return data def _strip_qt_binding_prefix(self, obj, data): """Strip binding specific prefix from type string.""" parts = obj.__class__.__module__.split('.') if len(parts) > 1 and parts[1] == 'QtCore': prefix = '.'.join(parts[:2]) data = data.replace(prefix, 'QtCore', 1) return data
class PerspectiveManager(QObject): """Manager for perspectives associated with specific sets of `Settings`.""" perspective_changed_signal = Signal(basestring) save_settings_signal = Signal(Settings, Settings) restore_settings_signal = Signal(Settings, Settings) restore_settings_without_plugin_changes_signal = Signal(Settings, Settings) HIDDEN_PREFIX = '@' def __init__(self, settings, application_context): super(PerspectiveManager, self).__init__() self.setObjectName('PerspectiveManager') self._qtgui_path = application_context.qtgui_path self._settings_proxy = SettingsProxy(settings) self._global_settings = Settings(self._settings_proxy, 'global') self._perspective_settings = None self._create_perspective_dialog = None self._menu_manager = None self._perspective_mapper = None # get perspective list from settings self.perspectives = self._settings_proxy.value('', 'perspectives', []) if isinstance(self.perspectives, basestring): self.perspectives = [self.perspectives] self._current_perspective = None self._remove_action = None self._callback = None self._callback_args = [] if application_context.provide_app_dbus_interfaces: from .perspective_manager_dbus_interface import PerspectiveManagerDBusInterface self._dbus_server = PerspectiveManagerDBusInterface( self, application_context) def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def set_perspective(self, name, hide_and_without_plugin_changes=False): if name is None: name = self._settings_proxy.value('', 'current-perspective', 'Default') elif hide_and_without_plugin_changes: name = self.HIDDEN_PREFIX + name self.switch_perspective( name, save_before=not hide_and_without_plugin_changes, without_plugin_changes=hide_and_without_plugin_changes) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) def switch_perspective(self, name, settings_changed=True, save_before=True, without_plugin_changes=False): if save_before and self._global_settings is not None and self._perspective_settings is not None: self._callback = self._switch_perspective self._callback_args = [name, settings_changed, save_before] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._switch_perspective(name, settings_changed, save_before, without_plugin_changes) def _switch_perspective(self, name, settings_changed, save_before, without_plugin_changes=False): # convert from unicode name = str(name.replace('/', '__')) qDebug( 'PerspectiveManager.switch_perspective() switching to perspective "%s"' % name) if self._current_perspective is not None and self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, False) self._menu_manager.set_item_disabled(self._current_perspective, False) # create perspective if necessary if name not in self.perspectives: self._create_perspective(name, clone_perspective=False) # update current perspective self._current_perspective = name if self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, True) self._menu_manager.set_item_disabled(self._current_perspective, True) if not self._current_perspective.startswith(self.HIDDEN_PREFIX): self._settings_proxy.set_value('', 'current-perspective', self._current_perspective) self._perspective_settings = self._get_perspective_settings( self._current_perspective) # emit signals self.perspective_changed_signal.emit( self._current_perspective.lstrip(self.HIDDEN_PREFIX)) if settings_changed: if not without_plugin_changes: self.restore_settings_signal.emit(self._global_settings, self._perspective_settings) else: self.restore_settings_without_plugin_changes_signal.emit( self._global_settings, self._perspective_settings) def save_settings_completed(self): if self._callback is not None: callback = self._callback callback_args = self._callback_args self._callback = None self._callback_args = [] callback(*callback_args) def _get_perspective_settings(self, perspective_name): return Settings(self._settings_proxy, 'perspective/%s' % perspective_name) def _on_create_perspective(self): name = self._choose_new_perspective_name() if name is not None: clone_perspective = self._create_perspective_dialog.clone_checkbox.isChecked( ) self._create_perspective(name, clone_perspective) self.switch_perspective(name, settings_changed=not clone_perspective, save_before=False) def _choose_new_perspective_name(self, show_cloning=True): # input dialog for new perspective name if self._create_perspective_dialog is None: ui_file = os.path.join(self._qtgui_path, 'resource', 'perspective_create.ui') self._create_perspective_dialog = loadUi(ui_file) # custom validator preventing forward slashs class CustomValidator(QValidator): def __init__(self, parent=None): super(CustomValidator, self).__init__(parent) def fixup(self, value): value = value.replace('/', '') def validate(self, value, pos): if value.find('/') != -1: pos = value.find('/') return (QValidator.Invalid, value, pos) if value == '': return (QValidator.Intermediate, value, pos) return (QValidator.Acceptable, value, pos) self._create_perspective_dialog.perspective_name_edit.setValidator( CustomValidator()) # set default values self._create_perspective_dialog.perspective_name_edit.setText('') self._create_perspective_dialog.clone_checkbox.setChecked(True) self._create_perspective_dialog.clone_checkbox.setVisible(show_cloning) # show dialog and wait for it's return value return_value = self._create_perspective_dialog.exec_() if return_value == self._create_perspective_dialog.Rejected: return name = str(self._create_perspective_dialog.perspective_name_edit.text( )).lstrip(self.HIDDEN_PREFIX) if name == '': QMessageBox.warning( self._menu_manager.menu, self.tr('Empty perspective name'), self.tr('The name of the perspective must be non-empty.')) return if name in self.perspectives: QMessageBox.warning( self._menu_manager.menu, self.tr('Duplicate perspective name'), self.tr('A perspective with the same name already exists.')) return return name def _create_perspective(self, name, clone_perspective=True): # convert from unicode name = str(name) if name.find('/') != -1: raise RuntimeError( 'PerspectiveManager._create_perspective() name must not contain forward slashs (/)' ) qDebug('PerspectiveManager._create_perspective(%s, %s)' % (name, clone_perspective)) # add to list of perspectives self.perspectives.append(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # save current settings if self._global_settings is not None and self._perspective_settings is not None: self._callback = self._create_perspective_continued self._callback_args = [name, clone_perspective] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._create_perspective_continued(name, clone_perspective) def _create_perspective_continued(self, name, clone_perspective): # clone settings if clone_perspective: new_settings = self._get_perspective_settings(name) keys = self._perspective_settings.all_keys() for key in keys: value = self._perspective_settings.value(key) new_settings.set_value(key, value) # add and switch to perspective if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True) def _on_remove_perspective(self): # input dialog to choose perspective to be removed names = list(self.perspectives) names.remove(self._current_perspective) name, return_value = QInputDialog.getItem( self._menu_manager.menu, self._menu_manager.tr('Remove perspective'), self._menu_manager.tr('Select the perspective'), names, 0, False) # convert from unicode name = str(name) if return_value == QInputDialog.Rejected: return self._remove_perspective(name) def _remove_perspective(self, name): if name not in self.perspectives: raise UserWarning('unknown perspective: %s' % name) qDebug('PerspectiveManager._remove_perspective(%s)' % str(name)) # remove from list of perspectives self.perspectives.remove(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # remove settings settings = self._get_perspective_settings(name) settings.remove('') # remove from menu self._menu_manager.remove_item(name) # disable remove-action if self._menu_manager.count_items() < 2: self._remove_action.setEnabled(False) def _on_import_perspective(self): file_name, _ = QFileDialog.getOpenFileName( self._menu_manager.menu, self.tr('Import perspective from file'), None, self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return perspective_name = os.path.basename(file_name) suffix = '.perspective' if perspective_name.endswith(suffix): perspective_name = perspective_name[:-len(suffix)] if perspective_name in self.perspectives: perspective_name = self._choose_new_perspective_name(False) if perspective_name is None: return self.import_perspective_from_file(file_name, perspective_name) def import_perspective_from_file(self, path, perspective_name): # create clean perspective if perspective_name in self.perspectives: self._remove_perspective(perspective_name) self._create_perspective(perspective_name, clone_perspective=False) # read perspective from file file_handle = open(path, 'r') #data = eval(file_handle.read()) data = json.loads(file_handle.read()) self._convert_values(data, self._import_value) new_settings = self._get_perspective_settings(perspective_name) self._set_dict_on_settings(data, new_settings) self.switch_perspective(perspective_name, settings_changed=True, save_before=True) def _set_dict_on_settings(self, data, settings): """Set dictionary key-value pairs on Settings instance.""" keys = data.get('keys', {}) for key in keys: settings.set_value(key, keys[key]) groups = data.get('groups', {}) for group in groups: sub = settings.get_settings(group) self._set_dict_on_settings(groups[group], sub) def _on_export_perspective(self): file_name, _ = QFileDialog.getSaveFileName( self._menu_manager.menu, self.tr('Export perspective to file'), self._current_perspective + '.perspective', self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return # trigger save of perspective before export self._callback = self._on_export_perspective_continued self._callback_args = [file_name] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) def _on_export_perspective_continued(self, file_name): # convert every value data = self._get_dict_from_settings(self._perspective_settings) self._convert_values(data, self._export_value) # write perspective data to file file_handle = open(file_name, 'w') file_handle.write(json.dumps(data, indent=2)) file_handle.close() def _get_dict_from_settings(self, settings): """Convert data of Settings instance to dictionary.""" keys = {} for key in settings.child_keys(): keys[str(key)] = settings.value(key) groups = {} for group in settings.child_groups(): sub = settings.get_settings(group) groups[str(group)] = self._get_dict_from_settings(sub) return {'keys': keys, 'groups': groups} def _convert_values(self, data, convert_function): keys = data.get('keys', {}) for key in keys: keys[key] = convert_function(keys[key]) groups = data.get('groups', {}) for group in groups: self._convert_values(groups[group], convert_function) def _import_value(self, value): import QtCore # @UnusedImport if value['type'] == 'repr': return eval(value['repr']) elif value['type'] == 'repr(QByteArray.hex)': return QByteArray.fromHex(eval(value['repr(QByteArray.hex)'])) raise RuntimeError( 'PerspectiveManager._import_value() unknown serialization type (%s)' % value['type']) def _export_value(self, value): data = {} if value.__class__.__name__ == 'QByteArray': hex_value = value.toHex() data['repr(QByteArray.hex)'] = self._strip_qt_binding_prefix( hex_value, repr(hex_value)) data['type'] = 'repr(QByteArray.hex)' # add pretty print for better readability characters = '' for i in range(1, value.size(), 2): character = value.at(i) # output all non-control characters if character >= ' ' and character <= '~': characters += character else: characters += ' ' data['pretty-print'] = characters else: data['repr'] = self._strip_qt_binding_prefix(value, repr(value)) data['type'] = 'repr' # verify that serialized data can be deserialized correctly reimported = self._import_value(data) if reimported != value: raise RuntimeError( 'PerspectiveManager._export_value() stored value can not be restored (%s)' % type(value)) return data def _strip_qt_binding_prefix(self, obj, data): """Strip binding specific prefix from type string.""" parts = obj.__class__.__module__.split('.') if len(parts) > 1 and parts[1] == 'QtCore': prefix = '.'.join(parts[:2]) data = data.replace(prefix, 'QtCore', 1) return data
class QParameterTreeWidget(QTreeWidget): logger = Logger() def __init__(self, parent=None, logger=Logger()): QTreeWidget.__init__(self, parent) self.set_logger(logger) # init tree self.setHeaderLabels(["Name", "Type", "Value"]) self.sortItems(0, Qt.AscendingOrder) #self.setSelectionMode(QAbstractItemView.NoSelection) self.setContextMenuPolicy(Qt.CustomContextMenu) self.itemActivated.connect(self.edit_item) self.currentItemChanged.connect(self.current_item_changed) # context menu self.customContextMenuRequested.connect(self.context_menu_request) self._action_item_expand = QAction(QIcon.fromTheme('zoom-in'), 'Expand Selected', self) self._action_item_expand.triggered.connect( self._handle_action_item_expand) self._action_item_collapse = QAction(QIcon.fromTheme('zoom-out'), 'Collapse Selected', self) self._action_item_collapse.triggered.connect( self._handle_action_item_collapse) self._action_item_add = QAction(QIcon.fromTheme('list-add'), 'Add', self) self._action_item_add.setEnabled(False) # TODO self._action_item_remove = QAction(QIcon.fromTheme('list-remove'), 'Remove', self) self._action_item_remove.setEnabled(False) # TODO def set_logger(self, logger): self.logger = logger @Slot(QPoint) def context_menu_request(self, point): if self.selectionModel().hasSelection(): menu = QMenu(self) menu.addAction(self._action_item_add) menu.addAction(self._action_item_remove) menu.addSeparator() menu.addAction(self._action_item_expand) menu.addAction(self._action_item_collapse) menu.exec_(self.mapToGlobal(point)) @Slot() def _handle_action_item_collapse(self): self._handle_action_set_expanded(False) @Slot() def _handle_action_item_expand(self): self._handle_action_set_expanded(True) @Slot(bool) def _handle_action_set_expanded(self, expanded): def recursive_set_expanded(index): if (index != QModelIndex()) and (index.column() == 0): self.setExpanded(index, expanded) #for i in range(index.model().childCount()): # index.model().child(i).setExpanded(expanded) #for i in range(index.model().rowCount()): # recursive_set_expanded(index.child(i, 0)) for index in self.selectedIndexes(): recursive_set_expanded(index) @Slot(QTreeWidgetItem, int) def edit_item(self, item, column): if (column == 0) or (item.is_leaf() and (column == 2)): item.setFlags(item.flags() | Qt.ItemIsEditable) self.editItem(item, column) item.setFlags(item.flags() & ~Qt.ItemIsEditable) @Slot(QTreeWidgetItem, QTreeWidgetItem) def current_item_changed(self, prev, current): if prev is not None: if not prev.update_value(): self.logger.log_error("Couldn't update value for '" + prev.get_name() + "' in '" + prev.get_namespace() + "'. Check input syntax!") if current is not None: if not current.update_value(): self.logger.log_error("Couldn't update value for '" + current.get_name() + "' in '" + current.get_namespace() + "'. Check input syntax!") # set parameter set from msg def set_parameter_set(self, param_set_msg): self.clear() self.root = QParameterTreeWidgetItem(self, self.logger, name=param_set_msg.name.data) self.root.setExpanded(True) for p in param_set_msg.params: self.root.add_param(Parameter(msg=p)) # get parameter set as msg def get_parameter_set(self): params = self.root.get_params() # remove top-level namespace top_len = len(self.root.get_name()) + 2 for p in params: p.set_name(p.get_name()[top_len:]) # generate msg param_set_msg = ParameterSet() param_set_msg.name.data = self.root.get_name() for p in params: param_set_msg.parameters.append(p.to_msg()) return param_set_msg