def saveSettings(self): settings = QSettings() settings.remove("Preferences") settings.beginGroup("Preferences") # General: Geometry & State settings.setValue("RestoreApplicationGeometry", self._restoreApplicationGeometry) settings.setValue("RestoreApplicationState", self._restoreApplicationState) # General: Recently Opened Documents settings.setValue("MaximumRecentDocuments", self._maximumRecentDocuments) settings.setValue("RestoreRecentDocuments", self._restoreRecentDocuments) # Document Presets: Header Labels settings.setValue("DefaultHeaderLabelHorizontal", self._defaultHeaderLabelHorizontal.value) settings.setValue("DefaultHeaderLabelVertical", self._defaultHeaderLabelVertical.value) # Document Presets: Cell Counts settings.setValue("DefaultCellCountColumn", self._defaultCellCountColumn) settings.setValue("DefaultCellCountRow", self._defaultCellCountRow) settings.endGroup()
def delete_app_from_folder(self, appid: str, folder: str): assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if appid == settings.value(key): settings.remove(key) self.app_change.emit() return
def save(self): settings = QSettings() settings.beginGroup('profiles') settings.remove('') for p in self.profiles: settings.setValue(f'{p.name}/path', p.path) settings.setValue(f'{p.name}/mask', p.mask) settings.setValue(f'{p.name}/pattern', p.pattern) settings.endGroup() if self.active_profile is not None: settings.setValue('active_profile', self.active_profile.name)
def accept(self): super().accept() settings = QSettings() cykit_address = self.cykit_address_field.text() if cykit_address: settings.setValue('CyKitAddress', cykit_address) else: settings.remove('CyKitAddress') cykit_port = self.cykit_port_field.text() if cykit_port: settings.setValue('CyKitPort', cykit_port) else: settings.remove('CyKitPort')
def rmdir(self, folder: str): assert folder assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if "_" in key: continue try: int(key) except ValueError: continue self.add_app_to_folder(settings.value(key), "", _emit=False) settings.remove("") self.app_change.emit()
def _saveSettings(self): settings = QSettings() # Recent documents if not self._preferences.restoreRecentDocuments(): self._recentDocuments.clear() settings.remove("RecentDocuments") settings.beginWriteArray("RecentDocuments") for idx in range(len(self._recentDocuments)): settings.setArrayIndex(idx) settings.setValue("Document", self._recentDocuments[idx]) settings.endArray() # Application properties: Geometry geometry = self.saveGeometry() if self._preferences.restoreApplicationGeometry() else QByteArray() settings.setValue("Application/Geometry", geometry) # Application properties: State state = self.saveState() if self._preferences.restoreApplicationState() else QByteArray() settings.setValue("Application/State", state)
class AppStore(QObject): app_change = Signal() def __init__(self): super().__init__() self.settings = QSettings(self) qt_write_base = Path( QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)) qt_write_base.mkdir(parents=True, exist_ok=True) self.icon_write_dir = qt_write_base / "app_icons" self.icon_write_dir.mkdir(exist_ok=True) def has_default_apps(self) -> bool: return self.settings.value("apps/_meta/isDefaultLoaded") def load_default_apps(self): def on_progress(value): if value != self.default_app_progress.maximum(): return self.settings.setValue("apps/_meta/isDefaultLoaded", True) logger.info("Default apps loaded") self.default_app_thread.progress.disconnect(on_progress) del self.default_app_progress del self.default_app_thread self.default_app_progress = QProgressDialog("Loading default apps", "Cancel", 0, 100) self.default_app_thread = _FetchRegistryThread(self) self.default_app_thread.progress.connect( self.default_app_progress.setValue) self.default_app_thread.progress.connect(on_progress) self.default_app_thread.items.connect( self.default_app_progress.setMaximum) self.default_app_thread.label.connect( self.default_app_progress.setLabelText) self.default_app_progress.canceled.connect( self.default_app_thread.cancel) self.default_app_thread.start() self.default_app_progress.show() def add_app(self, manifest_url: str, manifest: AppManifest): appid = app_id(manifest_url) try: manifest["appUrl"] = urljoin(manifest_url, manifest["appUrl"]) manifest["iconUrl"] = (urljoin(manifest_url, manifest["iconUrl"]) if manifest["iconUrl"] else "") manifest["configUrl"] = urljoin(manifest_url, manifest["configUrl"]) except KeyError: raise AddAppError(manifest_url) if manifest["iconUrl"]: self.download_app_icon(appid, manifest["iconUrl"]) self.settings.setValue(f"apps/{appid}", json.dumps(manifest)) self.app_change.emit() logger.info("Application %s (%s:%s) installed", manifest["appName"], appid, manifest_url) def mkdir(self, folder: str): assert folder assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") settings.setValue("_dir", "true") self.app_change.emit() def add_app_to_folder(self, appid: str, folder: str = "", _emit=True): assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") last_id = 0 for key in settings.childKeys(): if "_" in key: continue last_id = max(int(key), last_id) if appid == settings.value(key): settings.endGroup() return keys = [int(x) for x in settings.childKeys() if "_" not in x] last_id = max(keys) if keys else 0 settings.endGroup() settings.setValue(f"apps/_folder/{folder}/{last_id + 1}", appid) if _emit: self.app_change.emit() def delete_app_from_folder(self, appid: str, folder: str): assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if appid == settings.value(key): settings.remove(key) self.app_change.emit() return def remove_app(self, appid: str): settings = QSettings() settings.beginGroup("apps/_folder/") def recurse(): for key in settings.childKeys(): if settings.value(key) == appid: settings.remove(key) for childGroup in settings.childGroups(): settings.beginGroup(childGroup) recurse() settings.endGroup() recurse() self.settings.remove(f"apps/{appid}") icon_file = self.icon_write_dir / (appid + ".png") icon_file.unlink(True) self.app_change.emit() def rmdir(self, folder: str): assert folder assert "/" not in folder settings = QSettings() settings.beginGroup(f"apps/_folder/{folder}") for key in settings.childKeys(): if "_" in key: continue try: int(key) except ValueError: continue self.add_app_to_folder(settings.value(key), "", _emit=False) settings.remove("") self.app_change.emit() def download_app_icon(self, appid: str, url: str): dest = self.icon_write_dir / (appid + ".png") req = requests.get(url) with dest.open("wb") as fp: fp.write(req.content) logger.info("App icon %s wrote to %s", appid, str(dest)) def icon(self, appid: str) -> Optional[QIcon]: fn = QStandardPaths.locate(QStandardPaths.AppConfigLocation, "app_icons/" + appid + ".png") if fn == "": return None return QIcon(QPixmap(fn)) def all_apps(self) -> Iterator[Tuple[str, AppManifest]]: settings = QSettings() settings.beginGroup("apps") for appid in settings.childKeys(): if appid.startswith("_"): continue manifest = json.loads(settings.value(appid)) yield appid, manifest settings.endGroup() def list_app(self, root: str) -> Iterator[Tuple[str, Union[AppManifest, None]]]: settings = QSettings() settings.beginGroup(f"apps/_folder/" + root) for key in settings.childGroups(): yield key, None for key in settings.childKeys(): try: int(key) except ValueError: continue appid = settings.value(key) yield appid, self[appid] def __iter__(self): yield from self.all_apps() def __getitem__(self, item: str) -> AppManifest: assert not item.startswith("_") return json.loads(self.settings.value(f"apps/{item}"))
class MainWindow(QMainWindow): """Main window of Cutevariant""" def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Cutevariant") self.toolbar = self.addToolBar("maintoolbar") self.toolbar.setObjectName("maintoolbar") # For window saveState self.setWindowIcon(QIcon(DIR_ICONS + "app.png")) self.setWindowFlags(Qt.WindowContextHelpButtonHint | self.windowFlags()) # Keep sqlite connection self.conn = None # App settings self.app_settings = QSettings() # State variable of application # store fields, source, filters, group_by, having data # Often changed by plugins self.state = State() # Central workspace self.central_tab = QTabWidget() self.footer_tab = QTabWidget() self.vsplit = QSplitter(Qt.Vertical) self.vsplit.addWidget(self.central_tab) self.vsplit.addWidget(self.footer_tab) self.setCentralWidget(self.vsplit) # Status Bar self.status_bar = QStatusBar() self.setStatusBar(self.status_bar) # Setup menubar self.setup_menubar() # Setup toobar under the menu bar self.setup_toolbar() # Register plugins self.plugins = { } # dict of names (not titles) as keys and widgets as values self.dialog_plugins = { } # dict of actions as keys and classes as values self.register_plugins() # Window geometry self.resize(600, 400) self.setGeometry(QApplication.instance().desktop().rect().adjusted( 100, 100, -100, -100)) self.setTabPosition(Qt.AllDockWidgetAreas, QTabWidget.North) # If True, the GUI settings are deleted when the app is closed self.requested_reset_ui = False # Restores the state of this mainwindow's toolbars and dockwidgets self.read_settings() # Auto open recent projects recent = self.get_recent_projects() if recent and os.path.isfile(recent[0]): self.open(recent[0]) def add_panel(self, widget, area=Qt.LeftDockWidgetArea): """Add given widget to a new QDockWidget and to view menu in menubar""" dock = QDockWidget() dock.setWindowTitle(widget.windowTitle()) dock.setWidget(widget) dock.setStyleSheet("QDockWidget { font: bold }") # Keep the attached dock to allow further clean deletion widget.dock = dock # Set the objectName for a correct restoration after saveState dock.setObjectName(str(widget.__class__)) self.addDockWidget(area, dock) self.view_menu.addAction(dock.toggleViewAction()) def register_plugins(self): """Dynamically load plugins to the window Wrapper of :meth:`register_plugin` Two types of plugins can be registered: - widget: Added to the GUI, - dialog: DialogBox accessible from the tool menu. `setting` type is handled separately in :meth:`cutevariant.gui.settings.load_plugins` """ LOGGER.info("MainWindow:: Registering plugins...") # Get classes of plugins # Don't forget to skip disabled plugins for extension in plugin.find_plugins(): self.register_plugin(extension) def register_plugin(self, extension): """Dynamically load plugins to the window Two types of plugins can be registered: - widget: Added to the GUI, - dialog: DialogBox accessible from the tool menu. `setting` type is handled separately in :meth:`cutevariant.gui.settings.load_plugins` Args: extension (dict): Extension dict. See :meth:`cutevariant.gui.plugin.find_plugins` """ LOGGER.debug("Extension: %s", extension) name = extension["name"] title = extension["title"] displayed_title = name if LOGGER.getEffectiveLevel( ) == DEBUG else title if "widget" in extension and extension["widget"].ENABLE: # New GUI widget plugin_widget_class = extension["widget"] # Setup new widget widget = plugin_widget_class(parent=self) if not widget.objectName(): LOGGER.debug( "widget '%s' has no objectName attribute; " "=> fallback to extension name", displayed_title, ) widget.setObjectName(name) # Set title widget.setWindowTitle(displayed_title) # WhatsThis content long_description = extension.get("long_description") if not long_description: long_description = extension.get("description") widget.setWhatsThis(long_description) # Register (launch first init on some of them) widget.on_register(self) if self.conn: # If register occurs after a project is loaded we must set its # connection attribute widget.on_open_project(self.conn) # Init mainwindow via the constructor or on_register if widget.mainwindow != self: LOGGER.error( "Bad plugin implementation, <mainwindow> plugin attribute is not set." ) widget.on_close() else: # Add new plugin to plugins already registered self.plugins[name] = widget # Set position on the GUI if plugin_widget_class.LOCATION == plugin.DOCK_LOCATION: self.add_panel(widget) if plugin_widget_class.LOCATION == plugin.CENTRAL_LOCATION: self.central_tab.addTab(widget, widget.windowTitle()) if plugin_widget_class.LOCATION == plugin.FOOTER_LOCATION: self.footer_tab.addTab(widget, widget.windowTitle()) if "dialog" in extension and extension["dialog"].ENABLE: # New tool menu entry plugin_dialog_class = extension["dialog"] # Add plugin to Tools menu dialog_action = self.tool_menu.addAction(displayed_title + "...") self.dialog_plugins[dialog_action] = plugin_dialog_class dialog_action.triggered.connect(self.show_dialog) def refresh_plugins(self, sender: plugin.PluginWidget = None): """Refresh all widget plugins It doesn't refresh the sender plugin, and not visible plugins. Args: sender (PluginWidget): from a plugin, you can pass "self" as argument """ for plugin_obj in self.plugins.values(): if plugin_obj is not sender and ( plugin_obj.isVisible() or plugin_obj.REFRESH_ONLY_VISIBLE is False): try: plugin_obj.on_refresh() except Exception as e: LOGGER.exception(e) def refresh_plugin(self, plugin_name: str): """Refresh a widget plugin identified by plugin_name It doesn't refresh the sender plugin Args: plugin_name (str): Name of the plugin to be refreshed """ if plugin_name in self.plugins: plugin_obj = self.plugins[plugin_name] plugin_obj.on_refresh() def setup_menubar(self): """Menu bar setup: items and actions .. note:: Setup tools menu that could be dynamically augmented by plugins. """ ## File Menu self.file_menu = self.menuBar().addMenu(self.tr("&File")) self.new_project_action = self.file_menu.addAction( FIcon(0xF01BA), self.tr("&New project"), self.new_project, QKeySequence.New) self.open_project_action = self.file_menu.addAction( FIcon(0xF095D), self.tr("&Open project..."), self.open_project, QKeySequence.Open, ) ### Recent projects self.recent_files_menu = self.file_menu.addMenu(self.tr("Open recent")) self.setup_recent_menu() ### Export projects as self.export_csv_action = self.file_menu.addAction( self.tr("Export as csv"), self.export_as_csv) self.export_ped_action = self.file_menu.addAction( self.tr("Export pedigree PED/PLINK"), self.export_ped) self.file_menu.addSeparator() ### Misc self.file_menu.addAction(FIcon(0xF0493), self.tr("Settings..."), self.show_settings) self.file_menu.addSeparator() self.file_menu.addAction(self.tr("&Quit"), self.close, QKeySequence.Quit) ## Edit # TODO: if variant_view plugin is not loaded, disable this menu entries... self.edit_menu = self.menuBar().addMenu(self.tr("&Edit")) self.edit_menu.addAction( FIcon(0xF018F), self.tr("&Copy selected variants"), self.copy_variants_to_clipboard, "ctrl+shift+c", ) self.edit_menu.addSeparator() self.edit_menu.addAction( FIcon(0xF0486), self.tr("&Select all variants"), self.select_all_variants, QKeySequence.SelectAll, ) ## View self.view_menu = self.menuBar().addMenu(self.tr("&View")) self.view_menu.addAction(self.tr("Reset widgets positions"), self.reset_ui) # Set toggle footer visibility action show_action = self.view_menu.addAction(FIcon(0xF018D), self.tr("Show bottom toolbar")) show_action.setCheckable(True) show_action.setChecked(True) show_action.toggled.connect(self.toggle_footer_visibility) self.view_menu.addSeparator() ## Tools self.tool_menu = self.menuBar().addMenu(self.tr("&Tools")) ## Help self.help_menu = self.menuBar().addMenu(self.tr("Help")) self.help_menu.addAction( FIcon(0xF02D6), self.tr("What's this"), QWhatsThis.enterWhatsThisMode, QKeySequence.WhatsThis, ) self.help_menu.addAction( FIcon(0xF059F), self.tr("Documentation..."), partial(QDesktopServices.openUrl, QUrl(cm.WIKI_URL, QUrl.TolerantMode)), ) self.help_menu.addAction( FIcon(0xF0A30), self.tr("Report a bug..."), partial(QDesktopServices.openUrl, QUrl(cm.REPORT_BUG_URL, QUrl.TolerantMode)), ) self.help_menu.addSeparator() self.help_menu.addAction(self.tr("About Qt..."), QApplication.instance().aboutQt) self.help_menu.addAction( QIcon(DIR_ICONS + "app.png"), self.tr("About Cutevariant..."), self.about_cutevariant, ) def setup_toolbar(self): """Top tool bar setup: items and actions .. note:: Require selection_widget and some actions of Menubar """ # Tool bar self.toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) self.toolbar.addAction(self.new_project_action) self.toolbar.addAction(self.open_project_action) self.toolbar.addAction(FIcon(0xF02D7), self.tr("Help"), QWhatsThis.enterWhatsThisMode) self.toolbar.addSeparator() def open(self, filepath): """Open the given db/project file .. note:: Called at the end of a project creation by the Wizard, and by Open/Open recent projects slots. :param filepath: Path of project file. :type filepath: <str> """ if not os.path.isfile(filepath): return # Save directory self.app_settings.setValue("last_directory", os.path.dirname(filepath)) # Create connection self.conn = get_sql_connection(filepath) try: # DB version filter db_version = get_metadatas(self.conn).get("cutevariant_version") if db_version and parse_version(db_version) < parse_version( MIN_AUTHORIZED_DB_VERSION): # Refuse to open blacklisted DB versions # Unversioned files are still accepted QMessageBox.critical( self, self.tr("Error while opening project"), self.tr("File: {} is too old; please create a new project." ).format(filepath), ) return self.open_database(self.conn) self.save_recent_project(filepath) except sqlite3.OperationalError as e: LOGGER.exception(e) QMessageBox.critical( self, self.tr("Error while opening project"), self.tr( "File: {}\nThe following exception occurred:\n{}").format( filepath, e), ) return # Show the project name in title and in status bar self.setWindowTitle("Cutevariant - %s" % os.path.basename(filepath)) self.status_bar.showMessage(self.tr("{} opened").format(filepath)) def open_database(self, conn): """Open the project file and populate widgets Args: conn (sqlite3.Connection): Sqlite3 Connection """ self.conn = conn # Clear memoization cache for count_cmd command.clear_cache_cmd() # Clear State variable of application # store fields, source, filters, group_by, having data self.state = State() for plugin_obj in self.plugins.values(): plugin_obj.on_open_project(self.conn) def save_recent_project(self, path): """Save current project into QSettings Args: path (str): path of project """ paths = self.get_recent_projects() if path in paths: paths.remove(path) paths.insert(0, path) self.app_settings.setValue("recent_projects", paths[:MAX_RECENT_PROJECTS]) def get_recent_projects(self): """Return the list of recent projects stored in settings Returns: list: Recent project paths """ # Reload last projects opened recent_projects = self.app_settings.value("recent_projects", list()) # Check if recent_projects is a list() (as expected) if isinstance(recent_projects, str): recent_projects = [recent_projects] # Return only existing project files return [p for p in recent_projects if os.path.exists(p)] def clear_recent_projects(self): """Slot to clear the list of recent projects""" self.app_settings.remove("recent_projects") self.setup_recent_menu() def setup_recent_menu(self): """Setup recent submenu with previously opened projects""" self.recent_files_menu.clear() for path in self.get_recent_projects(): self.recent_files_menu.addAction(path, self.on_recent_project_clicked) self.recent_files_menu.addSeparator() self.recent_files_menu.addAction(self.tr("Clear"), self.clear_recent_projects) def on_recent_project_clicked(self): """Slot to load a recent project""" action = self.sender() LOGGER.debug(action.text()) self.open(action.text()) def new_project(self): """Slot to allow creation of a project with the Wizard""" wizard = ProjectWizard() if not wizard.exec_(): return db_filename = (wizard.field("project_path") + QDir.separator() + wizard.field("project_name") + ".db") try: self.open(db_filename) except Exception as e: self.status_bar.showMessage(e.__class__.__name__ + ": " + str(e)) raise def open_project(self): """Slot to open an already existing project from a QFileDialog""" # Reload last directory used last_directory = self.app_settings.value("last_directory", QDir.homePath()) filepath, _ = QFileDialog.getOpenFileName( self, self.tr("Open project"), last_directory, self.tr("Cutevariant project (*.db)"), ) if filepath: try: self.open(filepath) except Exception as e: self.status_bar.showMessage(e.__class__.__name__ + ": " + str(e)) raise def export_as_csv(self): """Export variants into CSV file""" # Reload last directory used last_directory = self.app_settings.value("last_directory", QDir.homePath()) filepath, _ = QFileDialog.getSaveFileName(self, self.tr("Save project"), last_directory, self.tr("(*.csv)")) if filepath: with open(filepath, "w") as file: writer = CsvWriter(file) writer.save(self.conn) def export_ped(self): """Export samples into PED/PLINK file""" # Reload last directory used last_directory = self.app_settings.value("last_directory", QDir.homePath()) # noinspection PyCallByClass filepath, _ = QFileDialog.getSaveFileName(self, self.tr("Save project"), last_directory, "(*.tfam)") if filepath: filepath = filepath if filepath.endswith( ".tfam") else filepath + ".tfam" with open(filepath, "w") as file: writer = PedWriter(file) writer.save(self.conn) def show_settings(self): """Slot to show settings window""" widget = SettingsWidget(self) widget.uiSettingsChanged.connect(self.reload_ui) widget.exec_() def show_dialog(self): """Show Plugin dialog box after a click on the tool menu""" action = self.sender() if action in self.dialog_plugins: # Get class object and instantiate it dialog_class = self.dialog_plugins[action] # Send SQL connection dialog = dialog_class(conn=self.conn) dialog.mainwindow = self dialog.exec_() def about_cutevariant(self): """Slot to show about window""" dialog_window = AboutCutevariant(self) dialog_window.exec_() def reload_ui(self): """Reload *without* reset the positions of the widgets""" geometry = self.saveGeometry() ui_state = self.saveState() self.reset_ui() # Reload positions self.restoreGeometry(geometry) self.restoreState(ui_state) def reset_ui(self): """Reset the positions of docks (and their widgets) to the default state All the widgets are deleted and reinstantiated on the GUI. GUI settings via QSettings are also reset. """ # Set reset ui boolean (used by closeEvent) self.requested_reset_ui = True # Reset settings self.write_settings() # Remove widgets in QTabWidgets for index in range(self.central_tab.count()): self.central_tab.removeTab(index) for index in range(self.footer_tab.count()): self.footer_tab.removeTab(index) # Remove tool menu actions linked to the dialog plugins for action in self.dialog_plugins: # LOGGER.debug("Remove action <%s>", action.text()) self.tool_menu.removeAction(action) action.deleteLater() # Purge widgets (central/footer and others) and related docks for plugin_obj in self.plugins.values(): # LOGGER.debug("Remove plugin <%s>", plugin_obj) self.removeDockWidget(plugin_obj.dock) plugin_obj.on_close() # Clean registered plugins self.plugins = {} self.dialog_plugins = {} # Register new plugins self.register_plugins() self.open_database(self.conn) # Allow a user to save further modifications self.requested_reset_ui = False def deregister_plugin(self, extension): """Delete plugin from the UI; Called from app settings when a plugin is disabled. - dialogs plugins: Remove action from tool menu - widgets plugins: Delete widget and its dock if available via its on_close() method. Args: extension (dict): Extension dict. See :meth:`cutevariant.gui.plugin.find_plugins` """ name = extension["name"] title = extension["title"] displayed_title = name if LOGGER.getEffectiveLevel( ) == DEBUG else title # Remove tool menu actions linked to the dialog plugins for action in self.dialog_plugins: if action.text() == displayed_title: # LOGGER.debug("Remove action <%s>", action.text()) self.tool_menu.removeAction(action) action.deleteLater() del self.dialog_plugins[action] break # Purge widgets and related docks if name in self.plugins: plugin_obj = self.plugins[name] # LOGGER.debug("Remove plugin <%s>", plugin_obj) self.removeDockWidget(plugin_obj.dock) plugin_obj.on_close() del self.plugins[name] def copy_variants_to_clipboard(self): if "variant_view" in self.plugins: self.plugins["variant_view"].copy() def select_all_variants(self): """Select all elements in the current tab's view""" if "variant_view" in self.plugins: self.plugins["variant_view"].select_all() def closeEvent(self, event): """Save the current state of this mainwindow's toolbars and dockwidgets .. warning:: Make sure that the property objectName is unique for each QToolBar and QDockWidget added to the QMainWindow. .. note:: Reset windowState if asked. """ self.write_settings() super().closeEvent(event) def write_settings(self): """Store the state of this mainwindow. .. note:: This methods is called by closeEvent """ if self.requested_reset_ui: # Delete window state setting self.app_settings.remove("windowState") else: self.app_settings.setValue("geometry", self.saveGeometry()) # TODO: handle UI changes by passing UI_VERSION to saveState() self.app_settings.setValue("windowState", self.saveState()) def read_settings(self): """Restore the state of this mainwindow's toolbars and dockwidgets .. note:: If windowState is not stored, current state is written. """ # Init reset ui boolean self.requested_reset_ui = False self.restoreGeometry(QByteArray(self.app_settings.value("geometry"))) # TODO: handle UI changes by passing UI_VERSION to saveState() window_state = self.app_settings.value("windowState") if window_state: self.restoreState(QByteArray(window_state)) else: # Setting has been deleted: set the current default state # TODO: handle UI changes by passing UI_VERSION to saveState() self.app_settings.setValue("windowState", self.saveState()) def toggle_footer_visibility(self, visibility): """Toggle visibility of the bottom toolbar and all its plugins""" self.footer_tab.setVisible(visibility)
def _unregister_url_scheme_windows(self): reg_path = self.WIN_REG_PATH.format(self.URL_SCHEME) reg = QSettings(reg_path, QSettings.NativeFormat) reg.remove("")
class PluginManager(): """Creates and manages the plugins. Serves as a bridge between the editor and the plugins. """ def __init__(self, parent): self.parent = parent self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat) self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH, QSettings.IniFormat) self.theme = self.settings.value(c.THEME, defaultValue=c.THEME_D) self.get_keyboard_shortcuts() self.get_plugins() self.do_not_delete = [ c.SAVE_KEY, c.WORD_BY_WORD_KEY_NEXT, c.FORWARD_KEY, c.PLAY_PAUSE_KEY, c.BACKWARDS_KEY, c.HELP_KEY, c.WORD_BY_WORD_KEY_PREV ] def get_keyboard_shortcuts(self): """Saves the current occupied shortcuts in a list""" self.occupied_keys = [] for key in self.keyboard_settings.allKeys(): keyboard_value = self.keyboard_settings.value(key) if len(keyboard_value) != 0: self.occupied_keys.append(keyboard_value) def get_plugins(self): """Creates all Plugins and saves them in the plugin-list.""" self.plugin_list = [] for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH): for file in filenames: if c.PLUGIN_POST not in file or "__pycache__" in dirpath: continue try: module_path = os.path.join(dirpath, file) temp_spec = ilu.spec_from_file_location( file.split(".")[0], module_path) temp_module = ilu.module_from_spec(temp_spec) temp_spec.loader.exec_module(temp_module) temp_plugin = temp_module.Plugin(self) if issubclass(type(temp_plugin), IPlugin): self.plugin_list.append(temp_plugin) print("Imported {} as a Plugin".format(file)) except BaseException as e: print("Error while importing {} with Exception {}".format( file, e)) def get_plugin_licences(self): """Gets all used plugin-licences.""" licences = {} for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH): if dirpath.lower().endswith("licences"): for filename in filenames: licences[filename.split(".")[0]] = dirpath return licences def get_toolbar_actions(self, parent): """Queries all toolbar actions from the plugins. Handles the Keyboard-Shortcuts from this toolbar-actions and prevents overlapping. Args: parent: The Parent-Window. Returns: Returns list of the QActions from all plugins. """ actions = [] for plugin in self.plugin_list: plugin_actions = plugin.get_toolbar_action(parent) plugin_name = plugin.get_name() for action in plugin_actions: if action is None or plugin_name is None or not plugin_name: continue key_name = plugin_name.upper().replace(" ", "_") + "_KEY" saved_key = self.keyboard_settings.value(key_name, defaultValue=None) plugin_shortcut = action.shortcut() if saved_key is None and not plugin_shortcut.isEmpty(): plugin_shortcut_upper = plugin_shortcut.toString().upper() if plugin_shortcut_upper not in self.occupied_keys: self.keyboard_settings.setValue( key_name, plugin_shortcut_upper) self.occupied_keys.append(saved_key) else: self.keyboard_settings.setValue(key_name, "") self.do_not_delete.append(key_name) if saved_key is not None: action.setShortcut(QKeySequence(saved_key)) actions.append(action) self.clear_keyboard_settings() return actions def clear_keyboard_settings(self): """Deletes all unused shortcuts from the keyboard settings.""" all_keys = set(self.keyboard_settings.allKeys()) to_delete = all_keys.difference(set(self.do_not_delete)) for key in to_delete: print("removed", key) self.keyboard_settings.remove(key) def get_word_by_word_actions(self, word, meta_data, pos): """Queries all QPushButtons of the plugins. Args: word: str: The current word word_meta_data: List[dict]: The meta_data from the word. word_pos: int: The current word position """ for plugin in self.plugin_list: plugin.get_word_action(word, meta_data, pos) def project_loaded(self): """Calls the project_loaded method of all plugins.""" for plugin in self.plugin_list: plugin.project_loaded() def add_new_word_by_word_action(self, btns, name, word, word_pos): """Adds a new Word-Action (QPushButton) to the editor word by word list. Args: btns: The QPushButtons from the plugin. name: The Name of the Buttons. word: The word for which these buttons are. word_pos: The position of the word. """ btns = btns for btn in btns: btn.setShortcut(None) self.parent.add_new_word_by_word_action(btns, name, word, word_pos) def get_selection(self): """Gets the current selected word(s) in the editor. Returns: Selected word(s) """ return self.parent.get_selection() def replace_selection(self, new_word): """Replaces the current selection in the editor. Args: new_word: the word(s) which should replace the current selection """ self.parent.replace_selection(new_word) def get_text(self): """Returns the full text from the editor. Returns: The full text from the editor. """ return self.parent.get_text() def set_text(self, new_text): """Sets the text in the editor with the given new_text. Args: new_text: The new text. """ self.parent.set_text(new_text) def get_word_at(self, pos): """Gets a the word at the given position. Args: pos: Position of the desired word. Returns: The word at the position. """ return self.parent.get_word_at(pos) def set_word_at(self, word, pos, replace_old): """Sets the word at a given position. Args: word: The word which should be set. pos: The position on which the word should be set. replace_old: true: replace the old word on the positon, false: set it before. """ self.parent.set_word_at(word, pos, replace_old) def get_setting(self, key, default): """Get a specific setting. Args: key: The settings-key default: The default value if there is no setting for the key. Returns: The settings value for the given key or the default if nothing is there. """ return self.settings.value(key, defaultValue=default) def get_language(self): """Returns the language of the current project. Returns: The language of the current project. """ return self.parent.get_language() def set_hint_text(self, text): """Sets the hint text (left bottom corner) in the editor. Args: text: The Hint text. """ self.parent.set_hint_text(text) def set_text_with_line_breaks(self, new_text): """Sets the Text in the editor but tries to restore the given linebreaks. Args: new_text: The new text. """ self.parent.set_text_with_line_breaks(new_text) def get_project_folder_path(self): """Get the current project folder path Returns: Project folder path. """ return self.parent.get_project_folder_path()
class SettingsDialog(QWidget): def __init__(self, appctxt, config, config_file): super().__init__() ui_path = appctxt.get_resource('SettingsDialog.ui') self.ui = loader.load(ui_path, self) self.ui.show() self.config = config self.config_file = config_file # Set values self.ui.line_edit_api_token.setText(self.config.get('api_token')) self.ui.checkbox_run_at_startup.setChecked(self.config.get('run_at_startup')) self.ui.line_edit_upload_directory.setText(self.config.get('watch_dir')) self.ui.line_edit_account_id.setText(self.config.get('account_id')) # Connect self.ui.button_select_file.clicked.connect(self.file_dialog) self.ui.pushButton.clicked.connect(self.save_config) # PE not sure how to test this with unit tests RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" self.run_at_startup_settings = QSettings(RUN_PATH, QSettings.NativeFormat) def file_dialog(self): logging.info('file_dialog') watch_dir = str(QFileDialog.getExistingDirectory(self, "Select Upload Directory")) self.config.set('watch_dir', watch_dir) self.ui.line_edit_upload_directory.setText(self.config.get('watch_dir')) def save_config(self): logging.info(f'accept') self.config.set('api_token', self.ui.line_edit_api_token.text()) self.config.set('run_at_startup', self.ui.checkbox_run_at_startup.isChecked()) self.config.set('account_id', self.ui.line_edit_account_id.text()) # FIXME should restart watchdog thread # Run at startup if self.config.get('run_at_startup'): startup_loc = 'C:\\Program Files (x86)\\PWUploader\\PWUploader.exe' logging.debug(f'Set run at startup: {startup_loc}') self.run_at_startup_settings.setValue('PWUploader', startup_loc) else: self.run_at_startup_settings.remove('PWUploader') # App guardian # FIXME # if self.config.get('always_running'): # start_guardian_detached() # else: # kill_guardian() with open(self.config_file, 'w') as f: f.write(json.dumps(self.config.as_dict(), indent=2)) logging.info('config saved') self.ui.accept()
class Settings: def __init__(self): # Standard constructor stuff super().__init__() self.settings = QSettings() if not pygame.get_init(): pygame.init() # Instance variables self.exercises = [] self.instrument = int(self.settings.value('instrument')) # Load exercises from stored settings self.settings.beginReadArray('exercises') for ex in self.settings.allKeys(): if ex == 'size': continue self.exercises.append([ex, self.settings.value(ex)]) self.settings.endArray() def remove_exercise(self, exercise_name): # Replace self.exercises with a copy without the selected exercise new_exercises = [] for ex in self.exercises: if ex[0] != exercise_name: new_exercises.append(ex) self.exercises = new_exercises def reload_exercise(self, exercise_name, exercise_text): # Load all exercise names exercise_names = [] for ex in self.exercises: exercise_names.append(ex[0]) new_exercises = [] # If the reloaded exercise is existing then update it in memory, # otherwise just add it exercise_contents = exercise_text.split() if exercise_name in exercise_names: for ex in self.exercises: if ex[0] == exercise_name: new_exercises.append([ex[0], exercise_contents]) else: new_exercises.append([ex[0], ex[1]]) self.exercises = new_exercises else: self.exercises.append([exercise_name, exercise_contents]) def set_instrument(self, instrument_id): self.instrument = instrument_id def save_settings(self): self.settings.beginWriteArray('exercises') for key in self.settings.allKeys(): self.settings.remove(key) if key != 'size' else None for ex in self.exercises: self.settings.setValue(ex[0], ex[1]) self.settings.endArray() self.settings.setValue('instrument', self.instrument) self.settings.sync() def preview(self): pygame.mixer.music.stop() midi_file = tempfile.NamedTemporaryFile(delete=False) mid = MidiFile() track = MidiTrack() mid.tracks.append(track) instrument = self.instrument track.append(Message('program_change', program=instrument, time=0)) note = 60 track.append(Message('note_on', note=note, velocity=100, time=0)) track.append(Message('note_off', note=note, time=2000)) mid.save(file=midi_file) midi_file.flush() midi_file.close() pygame.mixer.music.load(midi_file.name) pygame.mixer.music.play() if 'WARMUPPY_KEEP_MIDI' in os.environ: copyfile(midi_file.name, os.environ['WARMUPPY_KEEP_MIDI']) os.remove(midi_file.name)