Skip to content

Commit

Permalink
FEATURE: remember the last selected directories on windows
Browse files Browse the repository at this point in the history
  • Loading branch information
amilcarlucas committed Apr 24, 2024
1 parent 5f2d25e commit b67abaa
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 8 deletions.
89 changes: 89 additions & 0 deletions MethodicConfigurator/backend_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from re import compile as re_compile
from re import match as re_match
from re import escape as re_escape
from re import sub as re_sub

# from sys import exit as sys_exit
# from logging import debug as logging_debug
Expand Down Expand Up @@ -476,6 +477,94 @@ def copy_fc_values_to_file(self, selected_file: str, params: Dict[str, float]):
logging_warning("Parameter %s not found in the current parameter file", param)
return ret

@staticmethod
def __get_settings_directory():
settings_directory_file = os_path.join(os_path.dirname(os_path.realpath(__file__)), "settings_directory")
with open(settings_directory_file, "r", encoding='utf-8') as settings_directory_file_contents:
settings_directory = settings_directory_file_contents.read().strip()

if not os_path.exists(settings_directory):
raise FileNotFoundError(f"The settings directory '{settings_directory}' does not exist.")
elif not os_path.isdir(settings_directory):
raise NotADirectoryError(f"The path '{settings_directory}' is not a directory.")

return settings_directory

@staticmethod
def __get_settings_as_dict():
# Define the path to the settings.json file
settings_path = os_path.join(LocalFilesystem.__get_settings_directory(), "settings.json")

# Initialize settings as an empty dictionary
settings = {}

# Try to read the existing settings
try:
with open(settings_path, "r", encoding='utf-8') as settings_file:
settings = json_load(settings_file)
except FileNotFoundError:
# If the file does not exist, it will be created later
pass

# Ensure the directory_selection key exists in the settings
if "directory_selection" not in settings:
settings["directory_selection"] = {}
return settings

@staticmethod
def __set_settings_from_dict(settings):
# Define the path to the settings.json file
settings_path = os_path.join(LocalFilesystem.__get_settings_directory(), "settings.json")

# Write the updated settings back to the file
with open(settings_path, "w", encoding='utf-8') as settings_file:
json_dump(settings, settings_file, indent=4)

@staticmethod
def store_recently_used_template_dirs(template_dir: str, new_base_dir: str):
settings = LocalFilesystem.__get_settings_as_dict()

# Regular expression pattern to match single backslashes
pattern = r"(?<!\\)\\(?!\\)"

# Replacement string
replacement = r"\\"

# Update the settings with the new values
settings["directory_selection"].update({
"template_dir": re_sub(pattern, replacement, template_dir),
"new_base_dir": re_sub(pattern, replacement, new_base_dir)
})

LocalFilesystem.__set_settings_from_dict(settings)

@staticmethod
def store_recently_used_vehicle_dir(vehicle_dir: str):
settings = LocalFilesystem.__get_settings_as_dict()

# Regular expression pattern to match single backslashes
pattern = r"(?<!\\)\\(?!\\)"

# Replacement string
replacement = r"\\"

# Update the settings with the new values
settings["directory_selection"].update({
"vehicle_dir": re_sub(pattern, replacement, vehicle_dir)
})

LocalFilesystem.__set_settings_from_dict(settings)

@staticmethod
def get_recently_used_dirs():
settings = LocalFilesystem.__get_settings_as_dict()

template_dir = settings["directory_selection"].get("template_dir", "")
new_base_dir = settings["directory_selection"].get("new_base_dir", "")
vehicle_dir = settings["directory_selection"].get("vehicle_dir", "")

return template_dir, new_base_dir, vehicle_dir

def write_last_written_filename(self, current_file: str):
try:
with open(os_path.join(self.vehicle_dir, 'last_written_filename.txt'), 'w', encoding='utf-8') as file:
Expand Down
22 changes: 14 additions & 8 deletions MethodicConfigurator/frontend_tkinter_directory_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class DirectorySelectionWidgets():
including a label, an entry field for displaying the selected directory, and a button for opening a
directory selection dialog.
"""
def __init__(self, parent, parent_frame, initialdir: str, label_text: str, # pylint: disable=R0913
def __init__(self, parent, parent_frame, initialdir: str, label_text: str, # pylint: disable=too-many-arguments
autoresize_width: bool, dir_tooltip: str, button_tooltip: str):
self.parent = parent
self.directory = deepcopy(initialdir)
Expand Down Expand Up @@ -96,7 +96,7 @@ def get_selected_directory(self):
return self.directory


class DirectoryNameWidgets(): # pylint: disable=R0903
class DirectoryNameWidgets(): # pylint: disable=too-few-public-methods
"""
A class to manage directory name selection widgets in the GUI.
Expand Down Expand Up @@ -130,9 +130,11 @@ class VehicleDirectorySelectionWidgets(DirectorySelectionWidgets):
directory selections. It includes additional logic for updating the local filesystem with the
selected vehicle directory and re-initializing the filesystem with the new directory.
"""
def __init__(self, parent, parent_frame, local_filesystem: LocalFilesystem, destroy_parent_on_open: bool):
def __init__(self, parent: tk, parent_frame: tk.Frame, # pylint: disable=too-many-arguments
local_filesystem: LocalFilesystem,
initial_dir: str, destroy_parent_on_open: bool) -> None:
# Call the parent constructor with the necessary arguments
super().__init__(parent, parent_frame, local_filesystem.vehicle_dir, "Vehicle directory:",
super().__init__(parent, parent_frame, initial_dir, "Vehicle directory:",
False,
"Vehicle-specific directory containing the intermediate\n"
"parameter files to be written to the flight controller",
Expand All @@ -148,6 +150,7 @@ def on_select_directory(self):
self.local_filesystem.re_init(self.directory, self.local_filesystem.vehicle_type)
files = list(self.local_filesystem.file_parameters.keys())
if files:
LocalFilesystem.store_recently_used_vehicle_dir(self.directory)
if hasattr(self.parent, 'file_selection_combobox'):
# Update the file selection combobox with the new files
self.parent.file_selection_combobox.set_entries_tupple(files, files[0])
Expand Down Expand Up @@ -183,10 +186,11 @@ def __init__(self, local_filesystem: LocalFilesystem):
self.introduction_label = tk.Label(self.root, text=introduction_text + \
"\nChoose one of the following two options:")
self.introduction_label.pack(expand=False, fill=tk.X, padx=6, pady=6)
self.create_option1_widgets(local_filesystem.vehicle_dir,
local_filesystem.vehicle_dir,
template_dir, new_base_dir, vehicle_dir = LocalFilesystem.get_recently_used_dirs()
self.create_option1_widgets(template_dir,
new_base_dir,
"MyVehicleName")
self.create_option2_widgets()
self.create_option2_widgets(vehicle_dir)

# Bind the close_connection_and_quit function to the window close event
self.root.protocol("WM_DELETE_WINDOW", self.close_and_quit)
Expand Down Expand Up @@ -233,7 +237,7 @@ def create_option1_widgets(self, initial_template_dir: str, initial_base_dir: st
"copy the template files from the (source) template directory to it and\n"
"load the newly created files into the application")

def create_option2_widgets(self):
def create_option2_widgets(self, initial_dir: str):
# Option 2 - Use an existing vehicle configuration directory
option2_label_frame = tk.LabelFrame(self.root, text="Option 2")
option2_label_frame.pack(expand=True, fill=tk.X, padx=6, pady=6)
Expand All @@ -244,6 +248,7 @@ def create_option2_widgets(self):
option2_label.pack(expand=False, fill=tk.X, padx=6)
self.connection_selection_widgets = VehicleDirectorySelectionWidgets(self, option2_label_frame,
self.local_filesystem,
initial_dir,
destroy_parent_on_open=True)
self.connection_selection_widgets.container_frame.pack(expand=True, fill=tk.X, padx=3, pady=5, anchor=tk.NW)

Expand Down Expand Up @@ -275,6 +280,7 @@ def create_new_vehicle_from_template(self):
self.local_filesystem.re_init(new_vehicle_dir, self.local_filesystem.vehicle_type)
files = list(self.local_filesystem.file_parameters.keys())
if files:
LocalFilesystem.store_recently_used_template_dirs(template_dir, new_base_dir)
self.root.destroy()
else:
# No intermediate parameter files were found in the source template directory
Expand Down
1 change: 1 addition & 0 deletions MethodicConfigurator/frontend_tkinter_parameter_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ def __create_conf_widgets(self, version: str):
# Create a new frame inside the config_subframe for the intermediate parameter file directory selection labels
# and directory selection button
directory_selection_frame = VehicleDirectorySelectionWidgets(self, config_subframe, self.local_filesystem,
self.local_filesystem.vehicle_dir,
destroy_parent_on_open=False)
directory_selection_frame.container_frame.pack(side=tk.LEFT, fill="x", expand=False, padx=(4, 6))

Expand Down
29 changes: 29 additions & 0 deletions windows/ardupilot_methodic_configurator.iss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Source: "..\vehicle_examples\diatone_taycan_mxc\4.5.1-params\*.*"; DestDir: "{co
Source: "..\vehicle_examples\diatone_taycan_mxc\4.6.0-DEV-params\*.*"; DestDir: "{commonappdata}\.ardupilot_methodic_configurator\vehicle_examples\diatone_taycan_mxc\4.6.0-DEV-params"; Flags: ignoreversion
Source: "..\windows\version.txt"; DestDir: "{commonappdata}\.ardupilot_methodic_configurator"; Flags: ignoreversion
Source: "..\windows\MethodicConfigurator.ico"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\settings_template.json"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\ArduPilot_icon.png"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\ArduPilot_logo.png"; DestDir: "{app}\_internal"; Flags: ignoreversion
Source: "..\MethodicConfigurator\file_documentation.json"; DestDir: "{app}\_internal"; Flags: ignoreversion
Expand Down Expand Up @@ -167,3 +168,31 @@ begin
RefreshEnvironment();
end;
end;
function ReplacePlaceholdersInFile(const FileName: string): Boolean;
var
FileContent: AnsiString;
UnicodeFileContent: String;
ProgData: String;
begin
if LoadStringFromFile(FileName, FileContent) then
begin
UnicodeFileContent := String(FileContent)
ProgData := ExpandConstant('{commonappdata}')
StringChangeEx(ProgData, '\', '\\', True)
StringChangeEx(UnicodeFileContent, '{PROGRAM_DATA}', ProgData, True);
Result := SaveStringToFile(FileName, AnsiString(UnicodeFileContent), False);
end
else
Result := False;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
ReplacePlaceholdersInFile(ExpandConstant('{app}\_internal\settings_template.json'));
// Optionally, rename the file to settings.json if you want to use the original file name
RenameFile(ExpandConstant('{app}\_internal\settings_template.json'), ExpandConstant('{app}\_internal\settings.json'));
end;
end;
8 changes: 8 additions & 0 deletions windows/settings_template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Format version": 1,
"directory_selection": {
"template_dir": "{PROGRAM_DATA}\\.ardupilot_methodic_configurator\\vehicle_examples\\diatone_taycan_mxc\\4.5.1-params",
"new_base_dir": "{PROGRAM_DATA}\\.ardupilot_methodic_configurator\\vehicle_examples\\",
"vehicle_dir": "{PROGRAM_DATA}\\.ardupilot_methodic_configurator\\vehicle_examples\\"
}
}

0 comments on commit b67abaa

Please sign in to comment.