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.
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)

View File

@ -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
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 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()

View File

@ -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:

View File

@ -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