diff --git a/Scripts/Global.gd b/Scripts/Global.gd index 9f148dbe4..f9132733a 100644 --- a/Scripts/Global.gd +++ b/Scripts/Global.gd @@ -7,7 +7,8 @@ enum Brush_Types {PIXEL, CIRCLE, FILLED_CIRCLE, FILE, RANDOM_FILE, CUSTOM} var root_directory := "." var window_title := "" setget title_changed # Why doesn't Godot have get_window_title()? var config_cache := ConfigFile.new() -var directory_module := preload("res://Scripts/XDGDataPaths.gd") +var XDGDataPaths = preload("res://Scripts/XDGDataPaths.gd") +var directory_module : Node # warning-ignore:unused_class_variable var loaded_locales : Array @@ -264,6 +265,10 @@ func _ready() -> void: root_directory = OS.get_executable_path().get_base_dir() # Load settings from the config file config_cache.load("user://cache.ini") + + # The fact that root_dir is set earlier than this is important + # XDGDataDirs depends on it nyaa + directory_module = XDGDataPaths.new() undo_redo = UndoRedo.new() transparent_background = ImageTexture.new() diff --git a/Scripts/Palette/EditPalettePopup.gd b/Scripts/Palette/EditPalettePopup.gd index cfa7df3dd..7e495bcbc 100644 --- a/Scripts/Palette/EditPalettePopup.gd +++ b/Scripts/Palette/EditPalettePopup.gd @@ -106,12 +106,31 @@ func re_index_swatches() -> void: child.index = index index += 1 +# Rename a palette, copying to user directory if necessary. +func rename_palette_file_with_priority_dirs(old_fname: String, new_fname: String) -> void: + var user_write_directory: String = Global.directory_module.get_palette_write_path() + var usrwrite_dir := Directory.new() + usrwrite_dir.open(user_write_directory) + if usrwrite_dir.file_exists(old_fname): + usrwrite_dir.rename(old_fname, new_fname) + else: + # Scan through the main system directories + var priority_dirs : Array = Global.directory_module.get_palette_search_path_in_order() + var best_clone_location = Global.palette_container.get_best_palette_file_location( + priority_dirs, + old_fname + ) + if best_clone_location != null: + usrwrite_dir.copy(best_clone_location, new_fname) + + func _on_EditPaletteSaveButton_pressed() -> void: if palette_name_edit.text != current_palette: Global.palettes.erase(current_palette) - var dir := Directory.new() - dir.open(Global.root_directory) - dir.rename("Palettes".plus_file(current_palette + ".json"), "Palettes".plus_file(palette_name_edit.text + ".json")) + rename_palette_file_with_priority_dirs( + current_palette + ".json", + palette_name_edit.text + ".json" + ) current_palette = palette_name_edit.text working_palette.name = current_palette diff --git a/Scripts/Palette/PaletteContainer.gd b/Scripts/Palette/PaletteContainer.gd index b42d13097..b4bca4783 100644 --- a/Scripts/Palette/PaletteContainer.gd +++ b/Scripts/Palette/PaletteContainer.gd @@ -155,37 +155,38 @@ func on_color_select(index : int) -> void: Global.update_right_custom_brush() func _load_palettes() -> void: - # These are the paths for looking for palettes. + Global.directory_module.ensure_xdg_user_dirs_exist() + var search_locations = Global.directory_module.get_palette_search_path_in_order() + var priority_ordered_files := get_palette_priority_file_map(search_locations) - # The user path for palettes. - # this directory is created if it does not exist. - var user_palette_directory := Global.directory_module.get_palette_write_path() - - palettes_path = Global.root_directory.plus_file("Palettes") - var dir := Directory.new() - dir.open(Global.root_directory) - - if not dir.dir_exists(palettes_path): - dir.make_dir(palettes_path) - - var palette_files : Array = get_palette_files(palettes_path) - - for file_name in palette_files: - var palette : Palette = Palette.new().load_from_file(palettes_path.plus_file(file_name)) - if palette: - Global.palettes[palette.name] = palette - Global.palette_option_button.add_item(palette.name) - var index: int = Global.palette_option_button.get_item_count() - 1 - Global.palette_option_button.set_item_metadata(index, palette.name) - if palette.name == "Default": - Global.palette_option_button.select(index) + # Iterate backwards, so any palettes defined in default files + # get overwritten by those of the same name in user files + search_locations.invert() + priority_ordered_files.invert() + for i in range(len(search_locations)): + var base_directory : String = search_locations[i] + var palette_files : Array = priority_ordered_files[i] + for file_name in palette_files: + var palette : Palette = Palette.new().load_from_file(base_directory.plus_file(file_name)) + if palette: + Global.palettes[palette.name] = palette + Global.palette_option_button.add_item(palette.name) + var index: int = Global.palette_option_button.get_item_count() - 1 + Global.palette_option_button.set_item_metadata(index, palette.name) + if palette.name == "Default": + Global.palette_option_button.select(index) if not "Default" in Global.palettes && Global.palettes.size() > 0: Global.control._on_PaletteOptionButton_item_selected(0) -func get_palette_files(path : String) -> Array: +# Get the palette files in a single directory. +# if it does not exist, return [] +func get_palette_files(path : String ) -> Array: var dir := Directory.new() var results = [] + + if not dir.dir_exists(path): + return [] dir.open(path) dir.list_dir_begin() @@ -194,16 +195,59 @@ func get_palette_files(path : String) -> Array: var file_name = dir.get_next() if file_name == "": break - elif not file_name.begins_with(".") && file_name.to_lower().ends_with("json"): + elif (not file_name.begins_with(".")) && file_name.to_lower().ends_with("json") && not dir.current_is_dir(): results.append(file_name) dir.list_dir_end() return results +# This returns an array of arrays, with priorities. +# In particular, it takes an array of paths to look for +# arrays in, in order of file and palette override priority +# such that the files in the first directory override the +# second, third, etc. ^.^ +# It returns an array of arrays, where each output array +# corresponds to the given input array at the same index, and +# contains the (relative to the given directory) palette files +# to load, excluding all ones already existing in higher-priority +# directories. nya +# in particular, this also means you can run backwards on the result +# so that palettes with the given palette name in the higher priority +# directories override those set in lower priority directories :) +func get_palette_priority_file_map(looking_paths: Array) -> Array: + var final_list := [] + # Holds pattern files already found + var working_file_set : Dictionary = {} + for search_directory in looking_paths: + var to_add_files := [] + var files = get_palette_files(search_directory) + # files to check + for maybe_to_add in files: + if not maybe_to_add in working_file_set: + to_add_files.append(maybe_to_add) + working_file_set[maybe_to_add] = true + + final_list.append(to_add_files) + return final_list + +# Locate the highest priority palette by the given relative filename +# If none is found in the directories, then do nothing and return +# null +func get_best_palette_file_location(looking_paths: Array, fname: String): # -> String: + var priority_fmap : Array = get_palette_priority_file_map(looking_paths) + for i in range(len(looking_paths)): + var base_path : String = looking_paths[i] + var the_files : Array = priority_fmap[i] + if the_files.has(fname): + return base_path.plus_file(fname) + + return null + func save_palette(palette_name : String, filename : String) -> void: + Global.directory_module.ensure_xdg_user_dirs_exist() var palette = Global.palettes[palette_name] - var palettes_write_path:= Global.directory_module.get_palette_write_path() - palette.save_to_file(palettes_path.plus_file(filename)) + var palettes_write_path: String = Global.directory_module.get_palette_write_path() + palette.save_to_file(palettes_write_path.plus_file(filename)) func _on_NewPaletteDialog_popup_hide() -> void: diff --git a/Scripts/XDGDataPaths.gd b/Scripts/XDGDataPaths.gd index 308940256..afc2e1b01 100644 --- a/Scripts/XDGDataPaths.gd +++ b/Scripts/XDGDataPaths.gd @@ -1,8 +1,13 @@ extends Node +# These are *with* the config subdirectory name var xdg_data_home : String var xdg_data_dirs : Array +# These are *without* the config subdirectory name +var raw_xdg_data_home : String +var raw_xdg_data_dirs : Array + # Default location for xdg_data_home relative to $HOME const default_xdg_data_home_rel := ".local/share" const default_xdg_data_dirs := ["/usr/local/share", "/usr/share"] @@ -17,19 +22,30 @@ const brushes_data_subdirectory := "Brushes" # var b = "text" +# Get if we should use XDG standard or not nyaaaa +func use_xdg_standard() -> bool: + # see: https://docs.godotengine.org/en/latest/getting_started/workflow/export/feature_tags.html + return OS.has_feature("Linux") or OS.has_feature("BSD") + # Called when the node enters the scene tree for the first time. func _ready(): - if OS.has_feature("X11"): + pass + +func _init(): + if use_xdg_standard(): + print("Detected system where we should use XDG basedir standard (currently Linux or BSD)") var home := OS.get_environment("HOME") - xdg_data_home = home.plus_file( + raw_xdg_data_home = home.plus_file( default_xdg_data_home_rel - ).plus_file( + ) + xdg_data_home = raw_xdg_data_home.plus_file( config_subdir_name ) # Create defaults xdg_data_dirs = [] - for default_loc in default_xdg_data_dirs: + raw_xdg_data_dirs = default_xdg_data_dirs + for default_loc in raw_xdg_data_dirs: xdg_data_dirs.append( default_loc.plus_file(config_subdir_name) ) @@ -39,18 +55,22 @@ func _ready(): # See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html # Checks the xdg data home var if OS.has_environment("XDG_DATA_HOME"): - xdg_data_home = OS.get_environment("XDG_DATA_HOME").plus_file(config_subdir_name) + raw_xdg_data_home = OS.get_environment("XDG_DATA_HOME") + xdg_data_home = raw_xdg_data_home.plus_file(config_subdir_name) # Checks the list of files var, and processes them. if OS.has_environment("XDG_DATA_DIRS"): var raw_env_var := OS.get_environment("XDG_DATA_DIRS") # includes empties. var unappended_subdirs := raw_env_var.split(":", true) + raw_xdg_data_dirs = unappended_subdirs xdg_data_dirs = [] - for unapp_subdir in unappended_subdirs: + for unapp_subdir in raw_xdg_data_dirs: xdg_data_dirs.append(unapp_subdir.plus_file(config_subdir_name)) else: - xdg_data_home = Global.root_directory + raw_xdg_data_home = Global.root_directory + xdg_data_home = raw_xdg_data_home.plus_file(config_subdir_name) + raw_xdg_data_dirs = [] xdg_data_dirs = [] @@ -85,7 +105,26 @@ func get_palette_write_path() -> String: func get_brushes_write_path() -> String: return xdg_data_home.plus_file(brushes_data_subdirectory) - +# Ensure the user xdg directories exist: +func ensure_xdg_user_dirs_exist() -> void: + var base_dir := Directory.new() + base_dir.open(raw_xdg_data_home) + # Ensure the main config directory exists. + if not base_dir.dir_exists(xdg_data_home): + base_dir.make_dir(xdg_data_home) + + var actual_data_dir := Directory.new() + actual_data_dir.open(xdg_data_home) + var palette_writing_dir := get_palette_write_path() + var brushes_writing_dir := get_brushes_write_path() + # Create the palette and brush dirs + if not actual_data_dir.dir_exists(palette_writing_dir): + actual_data_dir.make_dir(palette_writing_dir) + if not actual_data_dir.dir_exists(brushes_writing_dir): + actual_data_dir.make_dir(brushes_writing_dir) + + + # Called every frame. 'delta' is the elapsed time since the previous frame. #func _process(delta):