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:
Lea Laux 2020-12-22 15:44:16 +01:00 committed by KDV Admin
parent 4369650972
commit 0bc39ada3d
6 changed files with 201 additions and 27 deletions

View File

@ -102,6 +102,13 @@ class AppConfigurator:
# Save the data in the configuration dictionary. # Save the data in the configuration dictionary.
self.save_configuration_data() 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: except Exception as file_error:
logging.error("The file {} cannot be opened and app configuration parameter cannot be loaded with the " 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) "following error: {}".format(self.yaml_app_configuration_file, file_error), exc_info=True)

View File

@ -102,7 +102,7 @@ class ConnectionStore:
except Exception as file_error: except Exception as file_error:
logging.error("The file {} cannot be opened and database connection parameter cannot be saved with the " 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): def check_parameter_for_duplicate(self, connection_parameter_dictionary_for_check):
""" """

83
pygadmin/file_manager.py Normal file
View 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()

View File

@ -2,6 +2,7 @@ import logging
import re import re
import datetime import datetime
from PyQt5 import QtGui
from PyQt5.Qsci import QsciScintilla from PyQt5.Qsci import QsciScintilla
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QEvent from PyQt5.QtCore import pyqtSlot, pyqtSignal, QEvent
from PyQt5.QtGui import QKeySequence 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_widget import SearchReplaceWidget
from pygadmin.widgets.search_replace_parent import SearchReplaceParent from pygadmin.widgets.search_replace_parent import SearchReplaceParent
from pygadmin.command_history_store import global_command_history_store from pygadmin.command_history_store import global_command_history_store
from pygadmin.file_manager import global_file_manager
class MetaEditor(type(QWidget), type(SearchReplaceParent)): 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_button.setEnabled(activation)
self.stop_query_shortcut.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 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, 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. 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. # Check if the class-wide variable for the corresponding file is None.
if self.corresponding_saved_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. # Open a file dialog and if the result is False, the process has been aborted.
@ -402,12 +408,31 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
# End the function with a return. # End the function with a return.
return return
try:
# Open the file in the write mode, so every content is also overwritten. # Open the file in the write mode, so every content is also overwritten.
with open(self.corresponding_saved_file, "w") as file_to_save: with open(self.corresponding_saved_file, "w") as file_to_save:
# Define the current text of the query input editor as current text. # Define the current text of the query input editor as current text.
current_text = self.query_input_editor.text() current_text = self.query_input_editor.text()
# Write the current text of the lexer as SQL editor in the file. # Write the current text of the lexer as SQL editor in the file.
file_to_save.write(current_text) 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. # Save the current text in the class-wide current editor text.
self.current_editor_text = current_text self.current_editor_text = current_text
@ -455,19 +480,45 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
# Get the file name out of the tuple. # Get the file name out of the tuple.
file_name = file_name_and_type[0] 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. # Check for the success in form of an existing file and not an empty string.
if file_name != "": 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. # Save the name of the file in the class variable for the corresponding file.
self.corresponding_saved_file = file_name self.corresponding_saved_file = file_name
# Open the file in reading mode. if global_app_configurator.get_single_configuration("open_previous_files") is True:
with open(self.corresponding_saved_file, "r") as file_to_load: global_file_manager.add_new_file(self.corresponding_saved_file)
# Read the whole given file and save its text. global_file_manager.commit_current_files_to_yaml()
file_text = file_to_load.read()
# Show the content of the file as text in the lexer as SQL query editor. # Show the content of the file as text in the lexer as SQL query editor.
self.query_input_editor.setText(file_text) 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 # Save the text of the file in the class-wide variable for the current text to check for changes and get the
# get the current state of saved/unsaved. # current state of saved/unsaved.
self.current_editor_text = file_text self.current_editor_text = file_text
# Update the window title # Update the window title
@ -476,13 +527,6 @@ class EditorWidget(QWidget, SearchReplaceParent, metaclass=MetaEditor):
# Report the success with a return value. # Report the success with a return value.
return True 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): 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 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. # Write a newline at the end of a data row.
file_to_save.write("\n") 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()

View File

@ -360,6 +360,14 @@ class MainWindow(QMainWindow):
# Check, if the current editor widget exists. # Check, if the current editor widget exists.
if current_editor_widget is not None: 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 # 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, # 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. # the process has been aborted.
@ -368,7 +376,7 @@ class MainWindow(QMainWindow):
return return
# Save the current statement and text in the query input editor with the function of the editor widget. # 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. # Define an else branch for error handling with a non existing current editor widget.
else: else:

View File

@ -5,8 +5,10 @@ from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QMdiArea from PyQt5.QtWidgets import QMdiArea
from PyQt5.QtCore import pyqtSlot, pyqtSignal from PyQt5.QtCore import pyqtSlot, pyqtSignal
from pygadmin.configurator import global_app_configurator
from pygadmin.widgets.editor import EditorWidget from pygadmin.widgets.editor import EditorWidget
from pygadmin.connectionfactory import global_connection_factory from pygadmin.connectionfactory import global_connection_factory
from pygadmin.file_manager import global_file_manager
class MdiArea(QMdiArea): 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. # 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.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): 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 Generate a new editor widget as sub window of the MdiArea and connect a signal for a change table/view for an