Open previous files/tabs after restart
Remember the open files in the editor tabs and reload them after restarting the application. This feature is configurable and described in issue #38
This commit is contained in:
		@@ -102,6 +102,13 @@ class AppConfigurator:
 | 
			
		||||
                # Save the data in the configuration dictionary.
 | 
			
		||||
                self.save_configuration_data()
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.configuration_dictionary["open_previous_files"]
 | 
			
		||||
 | 
			
		||||
            except KeyError:
 | 
			
		||||
                self.configuration_dictionary["open_previous_files"] = True
 | 
			
		||||
                self.save_configuration_data()
 | 
			
		||||
 | 
			
		||||
        except Exception as file_error:
 | 
			
		||||
            logging.error("The file {} cannot be opened and app configuration parameter cannot be loaded with the "
 | 
			
		||||
                          "following error: {}".format(self.yaml_app_configuration_file, file_error), exc_info=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -102,7 +102,7 @@ class ConnectionStore:
 | 
			
		||||
 | 
			
		||||
        except Exception as file_error:
 | 
			
		||||
            logging.error("The file {} cannot be opened and database connection parameter cannot be saved with the "
 | 
			
		||||
                          "following error: {}".format(self.yaml_connection_parameters_file, file_error))
 | 
			
		||||
                          "following error: {}".format(self.yaml_connection_parameters_file, file_error), exc_info=True)
 | 
			
		||||
 | 
			
		||||
    def check_parameter_for_duplicate(self, connection_parameter_dictionary_for_check):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								pygadmin/file_manager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								pygadmin/file_manager.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
import copy
 | 
			
		||||
import logging
 | 
			
		||||
import os
 | 
			
		||||
import yaml
 | 
			
		||||
 | 
			
		||||
from pygadmin.configurator import global_app_configurator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileManager:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.open_files = []
 | 
			
		||||
        # Define a path for the configuration files.
 | 
			
		||||
        configuration_path = os.path.join(os.path.expanduser("~"), '.pygadmin')
 | 
			
		||||
 | 
			
		||||
        # If the path for the configuration files does not exist, create it.
 | 
			
		||||
        if not os.path.exists(configuration_path):
 | 
			
		||||
            os.mkdir(configuration_path)
 | 
			
		||||
 | 
			
		||||
        # Define a yaml file, which stores the current open files in the editor widgets, so it is independent from the
 | 
			
		||||
        # user's operating system.
 | 
			
		||||
        self.open_files_file = os.path.join(configuration_path, "open_files.yaml")
 | 
			
		||||
 | 
			
		||||
        # Check for the existence of the file.
 | 
			
		||||
        if not os.path.exists(self.open_files_file):
 | 
			
		||||
            # Create the file as an empty file for writing in it.
 | 
			
		||||
            open(self.open_files_file, "a")
 | 
			
		||||
 | 
			
		||||
        if global_app_configurator.get_single_configuration("open_previous_files") is False:
 | 
			
		||||
            self.delete_all_files()
 | 
			
		||||
            self.commit_current_files_to_yaml()
 | 
			
		||||
 | 
			
		||||
    def add_new_file(self, file_name):
 | 
			
		||||
        self.open_files.append(file_name)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def delete_file(self, file_name):
 | 
			
		||||
        if file_name not in self.open_files:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        self.open_files.remove(file_name)
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def delete_all_files(self):
 | 
			
		||||
        self.open_files = []
 | 
			
		||||
 | 
			
		||||
    def commit_current_files_to_yaml(self):
 | 
			
		||||
        try:
 | 
			
		||||
            with open(self.open_files_file, "w") as file_data:
 | 
			
		||||
                yaml.safe_dump(self.open_files, file_data)
 | 
			
		||||
 | 
			
		||||
                return True
 | 
			
		||||
 | 
			
		||||
        except Exception as file_error:
 | 
			
		||||
            logging.error("The file {} cannot be opened and the open files in the editor cannot be saved with the "
 | 
			
		||||
                          "following error: {}".format(self.open_files_file, file_error), exc_info=True)
 | 
			
		||||
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def load_open_file_list(self):
 | 
			
		||||
        # Use a try statement in case of a broken .yaml file.
 | 
			
		||||
        try:
 | 
			
		||||
            # Use the read mode for getting the content of the file.
 | 
			
		||||
            with open(self.open_files_file, "r") as file_data:
 | 
			
		||||
                # Use the function for a safe load, because the file can be edited manually.
 | 
			
		||||
                self.open_files = yaml.safe_load(file_data)
 | 
			
		||||
 | 
			
		||||
                # If the list with the open files out of the .yaml file is empty, it is None after a load. But for
 | 
			
		||||
                # preventing further errors, because a list is expected, this if branch is used.
 | 
			
		||||
                if self.open_files is None:
 | 
			
		||||
                    # Define the list of open files as an empty list.
 | 
			
		||||
                    self.open_files = []
 | 
			
		||||
 | 
			
		||||
                # Return a copy of the list, so there is no manipulation from the outside.
 | 
			
		||||
                return copy.copy(self.open_files)
 | 
			
		||||
 | 
			
		||||
        except Exception as file_error:
 | 
			
		||||
            logging.error("The file {} cannot be opened previous open files cannot be loaded with the following "
 | 
			
		||||
                          "error: {}".format(self.open_files_file, file_error), exc_info=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
global_file_manager = FileManager()
 | 
			
		||||
@@ -2,6 +2,7 @@ import logging
 | 
			
		||||
import re
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
from PyQt5 import QtGui
 | 
			
		||||
from PyQt5.Qsci import QsciScintilla
 | 
			
		||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QEvent
 | 
			
		||||
from PyQt5.QtGui import QKeySequence
 | 
			
		||||
@@ -16,6 +17,7 @@ from pygadmin.database_query_executor import DatabaseQueryExecutor
 | 
			
		||||
from pygadmin.widgets.search_replace_widget import SearchReplaceWidget
 | 
			
		||||
from pygadmin.widgets.search_replace_parent import SearchReplaceParent
 | 
			
		||||
from pygadmin.command_history_store import global_command_history_store
 | 
			
		||||
from pygadmin.file_manager import global_file_manager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MetaEditor(type(QWidget), type(SearchReplaceParent)):
 | 
			
		||||
@@ -388,13 +390,17 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
 | 
			
		||||
            self.stop_query_button.setEnabled(activation)
 | 
			
		||||
            self.stop_query_shortcut.setEnabled(activation)
 | 
			
		||||
 | 
			
		||||
    def save_current_statement_in_file(self):
 | 
			
		||||
    def save_current_statement_in_file(self, previous_file_name=None):
 | 
			
		||||
        """
 | 
			
		||||
        Save the current text/statement of the lexer as query editor in for further usage. The class-wide variable for
 | 
			
		||||
        the corresponding file is used as directory with file. If this variable contains its initialized value None,
 | 
			
		||||
        use the function for opening a file dialog.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if previous_file_name is None and global_app_configurator.get_single_configuration("open_previous_files") is\
 | 
			
		||||
                True:
 | 
			
		||||
            previous_file_name = self.corresponding_saved_file
 | 
			
		||||
 | 
			
		||||
        # Check if the class-wide variable for the corresponding file is None.
 | 
			
		||||
        if self.corresponding_saved_file is None:
 | 
			
		||||
            # Open a file dialog and if the result is False, the process has been aborted.
 | 
			
		||||
@@ -402,14 +408,33 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
 | 
			
		||||
                # End the function with a return.
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
        # Open the file in the write mode, so every content is also overwritten.
 | 
			
		||||
        with open(self.corresponding_saved_file, "w") as file_to_save:
 | 
			
		||||
            # Define the current text of the query input editor as current text.
 | 
			
		||||
            current_text = self.query_input_editor.text()
 | 
			
		||||
            # Write the current text of the lexer as SQL editor in the file.
 | 
			
		||||
            file_to_save.write(current_text)
 | 
			
		||||
            # Save the current text in the class-wide current editor text.
 | 
			
		||||
            self.current_editor_text = current_text
 | 
			
		||||
        try:
 | 
			
		||||
            # Open the file in the write mode, so every content is also overwritten.
 | 
			
		||||
            with open(self.corresponding_saved_file, "w") as file_to_save:
 | 
			
		||||
                # Define the current text of the query input editor as current text.
 | 
			
		||||
                current_text = self.query_input_editor.text()
 | 
			
		||||
                # Write the current text of the lexer as SQL editor in the file.
 | 
			
		||||
                file_to_save.write(current_text)
 | 
			
		||||
 | 
			
		||||
        except Exception as file_error:
 | 
			
		||||
            error_text = "The file {} cannot be written with the error: {}".format(self.corresponding_saved_file,
 | 
			
		||||
                                                                                   file_error)
 | 
			
		||||
 | 
			
		||||
            QMessageBox.critical(self, "File Reading Error", error_text)
 | 
			
		||||
            logging.critical(error_text, exc_info=True)
 | 
			
		||||
 | 
			
		||||
            self.corresponding_saved_file = previous_file_name
 | 
			
		||||
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        if self.corresponding_saved_file != previous_file_name \
 | 
			
		||||
                and global_app_configurator.get_single_configuration("open_previous_files") is True:
 | 
			
		||||
            global_file_manager.delete_file(previous_file_name)
 | 
			
		||||
            global_file_manager.add_new_file(self.corresponding_saved_file)
 | 
			
		||||
            global_file_manager.commit_current_files_to_yaml()
 | 
			
		||||
 | 
			
		||||
        # Save the current text in the class-wide current editor text.
 | 
			
		||||
        self.current_editor_text = current_text
 | 
			
		||||
 | 
			
		||||
        # Update the current window title.
 | 
			
		||||
        self.update_window_title_and_description()
 | 
			
		||||
@@ -455,20 +480,46 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
 | 
			
		||||
        # Get the file name out of the tuple.
 | 
			
		||||
        file_name = file_name_and_type[0]
 | 
			
		||||
 | 
			
		||||
        if file_name is False:
 | 
			
		||||
            logging.info("The current file opening process was aborted by the user, so the content of this file is not "
 | 
			
		||||
                         "loaded.")
 | 
			
		||||
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return self.load_statement_with_file_name(file_name)
 | 
			
		||||
 | 
			
		||||
    def load_statement_with_file_name(self, file_name):
 | 
			
		||||
        # Check for the success in form of an existing file and not an empty string.
 | 
			
		||||
        if file_name != "":
 | 
			
		||||
            try:
 | 
			
		||||
                # Open the file in reading mode.
 | 
			
		||||
                with open(file_name, "r") as file_to_load:
 | 
			
		||||
                    # Read the whole given file and save its text.
 | 
			
		||||
                    file_text = file_to_load.read()
 | 
			
		||||
 | 
			
		||||
            except Exception as file_error:
 | 
			
		||||
                error_text = "The file {} cannot be loaded with the error: {}".format(file_name, file_error)
 | 
			
		||||
                QMessageBox.critical(self, "File Reading Error", error_text)
 | 
			
		||||
                logging.critical(error_text, exc_info=True)
 | 
			
		||||
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            if global_app_configurator.get_single_configuration("open_previous_files") is True and \
 | 
			
		||||
                    self.corresponding_saved_file is not None:
 | 
			
		||||
                    global_file_manager.delete_file(self.corresponding_saved_file)
 | 
			
		||||
 | 
			
		||||
            # Save the name of the file in the class variable for the corresponding file.
 | 
			
		||||
            self.corresponding_saved_file = file_name
 | 
			
		||||
 | 
			
		||||
            # Open the file in reading mode.
 | 
			
		||||
            with open(self.corresponding_saved_file, "r") as file_to_load:
 | 
			
		||||
                # Read the whole given file and save its text.
 | 
			
		||||
                file_text = file_to_load.read()
 | 
			
		||||
                # Show the content of the file as text in the lexer as SQL query editor.
 | 
			
		||||
                self.query_input_editor.setText(file_text)
 | 
			
		||||
                # Save the text of the file in the class-wide variable for the current text to check for changes and
 | 
			
		||||
                # get the current state of saved/unsaved.
 | 
			
		||||
                self.current_editor_text = file_text
 | 
			
		||||
            if global_app_configurator.get_single_configuration("open_previous_files") is True:
 | 
			
		||||
                global_file_manager.add_new_file(self.corresponding_saved_file)
 | 
			
		||||
                global_file_manager.commit_current_files_to_yaml()
 | 
			
		||||
 | 
			
		||||
            # Show the content of the file as text in the lexer as SQL query editor.
 | 
			
		||||
            self.query_input_editor.setText(file_text)
 | 
			
		||||
            # Save the text of the file in the class-wide variable for the current text to check for changes and get the
 | 
			
		||||
            # current state of saved/unsaved.
 | 
			
		||||
            self.current_editor_text = file_text
 | 
			
		||||
 | 
			
		||||
            # Update the window title
 | 
			
		||||
            self.update_window_title_and_description()
 | 
			
		||||
@@ -476,13 +527,6 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
 | 
			
		||||
            # Report the success with a return value.
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            logging.info("The current file opening process was aborted by the user, so the content of this file is not "
 | 
			
		||||
                         "loaded.")
 | 
			
		||||
 | 
			
		||||
            # Report the failure with a return value.
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def load_file_with_potential_overwrite_in_editor(self):
 | 
			
		||||
        """
 | 
			
		||||
        Load an existing file in the editor widget. This function is a wrapper for load_statement_out_of_file with more
 | 
			
		||||
@@ -989,3 +1033,11 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
 | 
			
		||||
                # Write a newline at the end of a data row.
 | 
			
		||||
                file_to_save.write("\n")
 | 
			
		||||
 | 
			
		||||
    def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
 | 
			
		||||
        if self.corresponding_saved_file is not None \
 | 
			
		||||
                and global_app_configurator.get_single_configuration("open_previous_files"):
 | 
			
		||||
            global_file_manager.delete_file(self.corresponding_saved_file)
 | 
			
		||||
            global_file_manager.commit_current_files_to_yaml()
 | 
			
		||||
 | 
			
		||||
        self.close()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -360,6 +360,14 @@ class MainWindow(QMainWindow):
 | 
			
		||||
 | 
			
		||||
        # Check, if the current editor widget exists.
 | 
			
		||||
        if current_editor_widget is not None:
 | 
			
		||||
            if global_app_configurator.get_single_configuration("open_previous_files"):
 | 
			
		||||
                # Get the current corresponding file name for the usage as previous file name, so an overwrite in the
 | 
			
		||||
                # editor for the global file manager can be realized.
 | 
			
		||||
                current_corresponding_file = current_editor_widget.corresponding_saved_file
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                current_corresponding_file = None
 | 
			
		||||
 | 
			
		||||
            # Check the parameter for save_as. If the parameter is True, the if clause gets to the point for a new
 | 
			
		||||
            # file dialog. If the result of this file dialog is False, end the function with a return. In this case,
 | 
			
		||||
            # the process has been aborted.
 | 
			
		||||
@@ -368,7 +376,7 @@ class MainWindow(QMainWindow):
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            # Save the current statement and text in the query input editor with the function of the editor widget.
 | 
			
		||||
            current_editor_widget.save_current_statement_in_file()
 | 
			
		||||
            current_editor_widget.save_current_statement_in_file(current_corresponding_file)
 | 
			
		||||
 | 
			
		||||
        # Define an else branch for error handling with a non existing current editor widget.
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
@@ -5,8 +5,10 @@ from PyQt5.QtGui import QIcon, QPixmap
 | 
			
		||||
from PyQt5.QtWidgets import QMdiArea
 | 
			
		||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal
 | 
			
		||||
 | 
			
		||||
from pygadmin.configurator import global_app_configurator
 | 
			
		||||
from pygadmin.widgets.editor import EditorWidget
 | 
			
		||||
from pygadmin.connectionfactory import global_connection_factory
 | 
			
		||||
from pygadmin.file_manager import global_file_manager
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MdiArea(QMdiArea):
 | 
			
		||||
@@ -45,6 +47,28 @@ class MdiArea(QMdiArea):
 | 
			
		||||
        # Use the empty icon as window icon, so the pygadmin logo is not in the window title bar of every editor widget.
 | 
			
		||||
        self.setWindowIcon(icon)
 | 
			
		||||
 | 
			
		||||
        self.init_open_files()
 | 
			
		||||
 | 
			
		||||
    def init_open_files(self):
 | 
			
		||||
        """
 | 
			
		||||
        Get the list of previous opened files of the editor out of the file manager and load them in new editor widgets.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if global_app_configurator.get_single_configuration("open_previous_files") is False:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # Get the files for opening.
 | 
			
		||||
        files_to_open = global_file_manager.load_open_file_list()
 | 
			
		||||
        # Delete all current files in the global file manager. They will be added after their load in the editor widget.
 | 
			
		||||
        global_file_manager.delete_all_files()
 | 
			
		||||
 | 
			
		||||
        # Add a widget for every file.
 | 
			
		||||
        for file in files_to_open:
 | 
			
		||||
            # Get a new editor.
 | 
			
		||||
            new_editor = self.generate_editor_tab()
 | 
			
		||||
            # Load the file in the editor.
 | 
			
		||||
            new_editor.load_statement_with_file_name(file)
 | 
			
		||||
 | 
			
		||||
    def generate_editor_tab(self):
 | 
			
		||||
        """
 | 
			
		||||
        Generate a new editor widget as sub window of the MdiArea and connect a signal for a change table/view for an
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user