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:
parent
4369650972
commit
0bc39ada3d
@ -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)
|
||||||
|
@ -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
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 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()
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user