987 lines
33 KiB
GDScript
987 lines
33 KiB
GDScript
@tool
|
|
extends Control
|
|
|
|
|
|
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
|
const DialogueSettings = preload("res://addons/dialogue_manager/components/settings.gd")
|
|
|
|
const OPEN_OPEN = 100
|
|
const OPEN_CLEAR = 101
|
|
|
|
const TRANSLATIONS_GENERATE_LINE_IDS = 100
|
|
const TRANSLATIONS_SAVE_CHARACTERS_TO_CSV = 201
|
|
const TRANSLATIONS_SAVE_TO_CSV = 202
|
|
const TRANSLATIONS_IMPORT_FROM_CSV = 203
|
|
|
|
const ITEM_SAVE = 100
|
|
const ITEM_SAVE_AS = 101
|
|
const ITEM_CLOSE = 102
|
|
const ITEM_CLOSE_ALL = 103
|
|
const ITEM_CLOSE_OTHERS = 104
|
|
const ITEM_COPY_PATH = 200
|
|
const ITEM_SHOW_IN_FILESYSTEM = 201
|
|
|
|
enum TranslationSource {
|
|
CharacterNames,
|
|
Lines
|
|
}
|
|
|
|
|
|
@onready var parse_timer := $ParseTimer
|
|
|
|
# Dialogs
|
|
@onready var new_dialog: FileDialog = $NewDialog
|
|
@onready var save_dialog: FileDialog = $SaveDialog
|
|
@onready var open_dialog: FileDialog = $OpenDialog
|
|
@onready var export_dialog: FileDialog = $ExportDialog
|
|
@onready var import_dialog: FileDialog = $ImportDialog
|
|
@onready var errors_dialog: AcceptDialog = $ErrorsDialog
|
|
@onready var settings_dialog: AcceptDialog = $SettingsDialog
|
|
@onready var settings_view := $SettingsDialog/SettingsView
|
|
@onready var build_error_dialog: AcceptDialog = $BuildErrorDialog
|
|
@onready var close_confirmation_dialog: ConfirmationDialog = $CloseConfirmationDialog
|
|
@onready var updated_dialog: AcceptDialog = $UpdatedDialog
|
|
|
|
# Toolbar
|
|
@onready var new_button: Button = %NewButton
|
|
@onready var open_button: MenuButton = %OpenButton
|
|
@onready var save_all_button: Button = %SaveAllButton
|
|
@onready var test_button: Button = %TestButton
|
|
@onready var search_button: Button = %SearchButton
|
|
@onready var insert_button: MenuButton = %InsertButton
|
|
@onready var translations_button: MenuButton = %TranslationsButton
|
|
@onready var settings_button: Button = %SettingsButton
|
|
@onready var docs_button: Button = %DocsButton
|
|
@onready var version_label: Label = %VersionLabel
|
|
@onready var update_button: Button = %UpdateButton
|
|
|
|
@onready var search_and_replace := %SearchAndReplace
|
|
|
|
# Code editor
|
|
@onready var content: HSplitContainer = %Content
|
|
@onready var files_list := %FilesList
|
|
@onready var files_popup_menu: PopupMenu = %FilesPopupMenu
|
|
@onready var title_list := %TitleList
|
|
@onready var code_edit := %CodeEdit
|
|
@onready var errors_panel := %ErrorsPanel
|
|
|
|
# The Dialogue Manager plugin
|
|
var editor_plugin: EditorPlugin
|
|
|
|
# The currently open file
|
|
var current_file_path: String = "":
|
|
set(next_current_file_path):
|
|
current_file_path = next_current_file_path
|
|
files_list.current_file_path = current_file_path
|
|
if current_file_path == "":
|
|
save_all_button.disabled = true
|
|
test_button.disabled = true
|
|
search_button.disabled = true
|
|
insert_button.disabled = true
|
|
translations_button.disabled = true
|
|
content.dragger_visibility = SplitContainer.DRAGGER_HIDDEN
|
|
files_list.hide()
|
|
title_list.hide()
|
|
code_edit.hide()
|
|
else:
|
|
test_button.disabled = false
|
|
search_button.disabled = false
|
|
insert_button.disabled = false
|
|
translations_button.disabled = false
|
|
content.dragger_visibility = SplitContainer.DRAGGER_VISIBLE
|
|
files_list.show()
|
|
title_list.show()
|
|
code_edit.show()
|
|
|
|
code_edit.text = open_buffers[current_file_path].text
|
|
code_edit.errors = []
|
|
code_edit.clear_undo_history()
|
|
code_edit.set_cursor(DialogueSettings.get_caret(current_file_path))
|
|
code_edit.grab_focus()
|
|
|
|
_on_code_edit_text_changed()
|
|
|
|
errors_panel.errors = []
|
|
code_edit.errors = []
|
|
get:
|
|
return current_file_path
|
|
|
|
# A reference to the currently open files and their last saved text
|
|
var open_buffers: Dictionary = {}
|
|
|
|
# Which thing are we exporting translations for?
|
|
var translation_source: TranslationSource = TranslationSource.Lines
|
|
|
|
|
|
func _ready() -> void:
|
|
apply_theme()
|
|
|
|
# Start with nothing open
|
|
self.current_file_path = ""
|
|
|
|
# Set up the update checker
|
|
version_label.text = "v%s" % update_button.get_version()
|
|
update_button.editor_plugin = editor_plugin
|
|
update_button.on_before_refresh = func on_before_refresh():
|
|
# Save everything
|
|
DialogueSettings.set_user_value("just_refreshed", {
|
|
current_file_path = current_file_path,
|
|
open_buffers = open_buffers
|
|
})
|
|
return true
|
|
|
|
# Did we just load from an addon version refresh?
|
|
var just_refreshed = DialogueSettings.get_user_value("just_refreshed", null)
|
|
if just_refreshed != null:
|
|
DialogueSettings.set_user_value("just_refreshed", null)
|
|
call_deferred("load_from_version_refresh", just_refreshed)
|
|
|
|
# Hook up the search toolbar
|
|
search_and_replace.code_edit = code_edit
|
|
|
|
# Connect menu buttons
|
|
insert_button.get_popup().id_pressed.connect(_on_insert_button_menu_id_pressed)
|
|
translations_button.get_popup().id_pressed.connect(_on_translations_button_menu_id_pressed)
|
|
|
|
code_edit.main_view = self
|
|
code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE
|
|
var editor_settings: EditorSettings = editor_plugin.get_editor_interface().get_editor_settings()
|
|
editor_settings.settings_changed.connect(_on_editor_settings_changed)
|
|
_on_editor_settings_changed()
|
|
|
|
save_all_button.disabled = true
|
|
|
|
close_confirmation_dialog.ok_button_text = DialogueConstants.translate("confirm_close.save")
|
|
close_confirmation_dialog.add_button(DialogueConstants.translate("confirm_close.discard"), true, "discard")
|
|
|
|
settings_view.editor_plugin = editor_plugin
|
|
|
|
errors_dialog.dialog_text = DialogueConstants.translate("errors_in_script")
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not visible: return
|
|
|
|
if event is InputEventKey and event.is_pressed():
|
|
match event.as_text():
|
|
"Ctrl+Alt+S":
|
|
save_file(current_file_path)
|
|
"Ctrl+W":
|
|
get_viewport().set_input_as_handled()
|
|
close_file(current_file_path)
|
|
"Ctrl+F5":
|
|
_on_test_button_pressed()
|
|
|
|
|
|
func apply_changes() -> void:
|
|
save_files()
|
|
|
|
|
|
# Load back to the previous buffer regardless of if it was actually saved
|
|
func load_from_version_refresh(just_refreshed: Dictionary) -> void:
|
|
if just_refreshed.has("current_file_content"):
|
|
# We just loaded from a version before multiple buffers
|
|
var file: FileAccess = FileAccess.open(just_refreshed.current_file_path, FileAccess.READ)
|
|
var file_text: String = file.get_as_text()
|
|
open_buffers[just_refreshed.current_file_path] = {
|
|
pristine_text = file_text,
|
|
text = just_refreshed.current_file_content
|
|
}
|
|
else:
|
|
open_buffers = just_refreshed.open_buffers
|
|
|
|
if just_refreshed.current_file_path != "":
|
|
editor_plugin.get_editor_interface().edit_resource(load(just_refreshed.current_file_path))
|
|
else:
|
|
editor_plugin.get_editor_interface().set_main_screen_editor("Dialogue")
|
|
|
|
updated_dialog.dialog_text = DialogueConstants.translate("update.success").format({ version = update_button.get_version() })
|
|
updated_dialog.popup_centered()
|
|
|
|
|
|
func new_file(path: String, content: String = "") -> void:
|
|
if open_buffers.has(path):
|
|
remove_file_from_open_buffers(path)
|
|
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
if content == "":
|
|
if DialogueSettings.get_setting("new_with_template", true):
|
|
file.store_string("\n".join([
|
|
"~ this_is_a_node_title",
|
|
"",
|
|
"Nathan: [[Hi|Hello|Howdy]], this is some dialogue.",
|
|
"Nathan: Here are some choices.",
|
|
"- First one",
|
|
"\tNathan: You picked the first one.",
|
|
"- Second one",
|
|
"\tNathan: You picked the second one.",
|
|
"- Start again => this_is_a_node_title",
|
|
"- End the conversation => END",
|
|
"Nathan: For more information see the online documentation.",
|
|
"",
|
|
"=> END"
|
|
]))
|
|
else:
|
|
file.store_string(content)
|
|
|
|
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
|
|
|
|
|
# Open a dialogue resource for editing
|
|
func open_resource(resource: DialogueResource) -> void:
|
|
open_file(resource.resource_path)
|
|
|
|
|
|
func open_file(path: String) -> void:
|
|
if not open_buffers.has(path):
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
var text = file.get_as_text()
|
|
|
|
open_buffers[path] = {
|
|
cursor = Vector2.ZERO,
|
|
text = text,
|
|
pristine_text = text
|
|
}
|
|
|
|
DialogueSettings.add_recent_file(path)
|
|
build_open_menu()
|
|
|
|
files_list.files = open_buffers.keys()
|
|
files_list.select_file(path)
|
|
|
|
self.current_file_path = path
|
|
|
|
|
|
func show_file_in_filesystem(path: String) -> void:
|
|
var file_system = editor_plugin.get_editor_interface().get_file_system_dock()
|
|
file_system.navigate_to_path(path)
|
|
|
|
|
|
# Save any open files
|
|
func save_files() -> void:
|
|
var saved_files: PackedStringArray = []
|
|
for path in open_buffers:
|
|
if open_buffers[path].text != open_buffers[path].pristine_text:
|
|
saved_files.append(path)
|
|
save_file(path)
|
|
|
|
# Make sure we reimport/recompile the changes
|
|
if saved_files.size() > 0:
|
|
editor_plugin.get_editor_interface().get_resource_filesystem().reimport_files(saved_files)
|
|
save_all_button.disabled = true
|
|
|
|
|
|
# Save a file
|
|
func save_file(path: String) -> void:
|
|
var buffer = open_buffers[path]
|
|
|
|
files_list.mark_file_as_unsaved(path, false)
|
|
save_all_button.disabled = files_list.unsaved_files.size() == 0
|
|
|
|
# Don't bother saving if there is nothing to save
|
|
if buffer.text == buffer.pristine_text:
|
|
return
|
|
|
|
buffer.pristine_text = buffer.text
|
|
|
|
# Save the current text
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
file.store_string(buffer.text)
|
|
file.close()
|
|
|
|
|
|
func close_file(file: String) -> void:
|
|
if not file in open_buffers.keys(): return
|
|
|
|
var buffer = open_buffers[file]
|
|
|
|
if buffer.text == buffer.pristine_text:
|
|
remove_file_from_open_buffers(file)
|
|
else:
|
|
close_confirmation_dialog.dialog_text = DialogueConstants.translate("confirm_close").format({ path = file.get_file() })
|
|
close_confirmation_dialog.popup_centered()
|
|
|
|
|
|
func remove_file_from_open_buffers(file: String) -> void:
|
|
if not file in open_buffers.keys(): return
|
|
|
|
var current_index = open_buffers.keys().find(file)
|
|
|
|
open_buffers.erase(file)
|
|
if open_buffers.size() == 0:
|
|
self.current_file_path = ""
|
|
else:
|
|
current_index = clamp(current_index, 0, open_buffers.size() - 1)
|
|
self.current_file_path = open_buffers.keys()[current_index]
|
|
files_list.files = open_buffers.keys()
|
|
|
|
|
|
# Apply theme colors and icons to the UI
|
|
func apply_theme() -> void:
|
|
if is_instance_valid(editor_plugin) and is_instance_valid(code_edit):
|
|
var scale: float = editor_plugin.get_editor_interface().get_editor_scale()
|
|
var editor_settings = editor_plugin.get_editor_interface().get_editor_settings()
|
|
code_edit.theme_overrides = {
|
|
scale = scale,
|
|
|
|
background_color = editor_settings.get_setting("text_editor/theme/highlighting/background_color"),
|
|
current_line_color = editor_settings.get_setting("text_editor/theme/highlighting/current_line_color"),
|
|
error_line_color = editor_settings.get_setting("text_editor/theme/highlighting/mark_color"),
|
|
|
|
titles_color = editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"),
|
|
text_color = editor_settings.get_setting("text_editor/theme/highlighting/text_color"),
|
|
conditions_color = editor_settings.get_setting("text_editor/theme/highlighting/keyword_color"),
|
|
mutations_color = editor_settings.get_setting("text_editor/theme/highlighting/function_color"),
|
|
members_color = editor_settings.get_setting("text_editor/theme/highlighting/member_variable_color"),
|
|
strings_color = editor_settings.get_setting("text_editor/theme/highlighting/string_color"),
|
|
numbers_color = editor_settings.get_setting("text_editor/theme/highlighting/number_color"),
|
|
symbols_color = editor_settings.get_setting("text_editor/theme/highlighting/symbol_color"),
|
|
comments_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_color"),
|
|
jumps_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"), 0.7),
|
|
|
|
font_size = editor_settings.get_setting("interface/editor/code_font_size")
|
|
}
|
|
|
|
new_button.icon = get_theme_icon("New", "EditorIcons")
|
|
new_button.tooltip_text = DialogueConstants.translate("start_a_new_file")
|
|
|
|
open_button.icon = get_theme_icon("Load", "EditorIcons")
|
|
open_button.tooltip_text = DialogueConstants.translate("open_a_file")
|
|
|
|
save_all_button.icon = get_theme_icon("Save", "EditorIcons")
|
|
save_all_button.tooltip_text = DialogueConstants.translate("start_all_files")
|
|
|
|
test_button.icon = get_theme_icon("PlayScene", "EditorIcons")
|
|
test_button.tooltip_text = DialogueConstants.translate("test_dialogue")
|
|
|
|
search_button.icon = get_theme_icon("Search", "EditorIcons")
|
|
search_button.tooltip_text = DialogueConstants.translate("search_for_text")
|
|
|
|
insert_button.icon = get_theme_icon("RichTextEffect", "EditorIcons")
|
|
insert_button.text = DialogueConstants.translate("insert")
|
|
|
|
translations_button.icon = get_theme_icon("Translation", "EditorIcons")
|
|
translations_button.text = DialogueConstants.translate("translations")
|
|
|
|
settings_button.icon = get_theme_icon("Tools", "EditorIcons")
|
|
settings_button.tooltip_text = DialogueConstants.translate("settings")
|
|
|
|
docs_button.icon = get_theme_icon("Help", "EditorIcons")
|
|
docs_button.text = DialogueConstants.translate("docs")
|
|
|
|
update_button.apply_theme()
|
|
|
|
# Set up the effect menu
|
|
var popup: PopupMenu = insert_button.get_popup()
|
|
popup.clear()
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.wave_bbcode"), 0)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.shake_bbcode"), 1)
|
|
popup.add_separator()
|
|
popup.add_icon_item(get_theme_icon("Time", "EditorIcons"), DialogueConstants.translate("insert.typing_pause"), 3)
|
|
popup.add_icon_item(get_theme_icon("ViewportSpeed", "EditorIcons"), DialogueConstants.translate("insert.typing_speed_change"), 4)
|
|
popup.add_icon_item(get_theme_icon("DebugNext", "EditorIcons"), DialogueConstants.translate("insert.auto_advance"), 5)
|
|
popup.add_separator(DialogueConstants.translate("insert.templates"))
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.title"), 6)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.dialogue"), 7)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.response"), 8)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.random_lines"), 9)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.random_text"), 10)
|
|
popup.add_separator(DialogueConstants.translate("insert.actions"))
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.jump"), 11)
|
|
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.end_dialogue"), 12)
|
|
|
|
# Set up the translations menu
|
|
popup = translations_button.get_popup()
|
|
popup.clear()
|
|
popup.add_icon_item(get_theme_icon("Translation", "EditorIcons"), DialogueConstants.translate("generate_line_ids"), TRANSLATIONS_GENERATE_LINE_IDS)
|
|
popup.add_separator()
|
|
popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate("save_characters_to_csv"), TRANSLATIONS_SAVE_CHARACTERS_TO_CSV)
|
|
popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate("save_to_csv"), TRANSLATIONS_SAVE_TO_CSV)
|
|
popup.add_icon_item(get_theme_icon("AssetLib", "EditorIcons"), DialogueConstants.translate("import_from_csv"), TRANSLATIONS_IMPORT_FROM_CSV)
|
|
|
|
# Dialog sizes
|
|
new_dialog.min_size = Vector2(600, 500) * scale
|
|
save_dialog.min_size = Vector2(600, 500) * scale
|
|
open_dialog.min_size = Vector2(600, 500) * scale
|
|
export_dialog.min_size = Vector2(600, 500) * scale
|
|
export_dialog.min_size = Vector2(600, 500) * scale
|
|
settings_dialog.min_size = Vector2(600, 600) * scale
|
|
|
|
|
|
### Helpers
|
|
|
|
|
|
# Refresh the open menu with the latest files
|
|
func build_open_menu() -> void:
|
|
var menu = open_button.get_popup()
|
|
menu.clear()
|
|
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DialogueConstants.translate("open.open"), OPEN_OPEN)
|
|
menu.add_separator()
|
|
|
|
var recent_files = DialogueSettings.get_recent_files()
|
|
if recent_files.size() == 0:
|
|
menu.add_item(DialogueConstants.translate("open.no_recent_files"))
|
|
menu.set_item_disabled(2, true)
|
|
else:
|
|
for path in recent_files:
|
|
menu.add_icon_item(get_theme_icon("File", "EditorIcons"), path)
|
|
|
|
menu.add_separator()
|
|
menu.add_item(DialogueConstants.translate("open.clear_recent_files"), OPEN_CLEAR)
|
|
if menu.id_pressed.is_connected(_on_open_menu_id_pressed):
|
|
menu.id_pressed.disconnect(_on_open_menu_id_pressed)
|
|
menu.id_pressed.connect(_on_open_menu_id_pressed)
|
|
|
|
|
|
# Get the last place a CSV, etc was exported
|
|
func get_last_export_path(extension: String) -> String:
|
|
var filename = current_file_path.get_file().replace(".dialogue", "." + extension)
|
|
return DialogueSettings.get_user_value("last_export_path", current_file_path.get_base_dir()) + "/" + filename
|
|
|
|
|
|
# Check the current text for errors
|
|
func parse() -> void:
|
|
# Skip if nothing to parse
|
|
if current_file_path == "": return
|
|
|
|
var parser = DialogueManagerParser.new()
|
|
var errors: Array[Dictionary] = []
|
|
if parser.parse(code_edit.text, current_file_path) != OK:
|
|
errors = parser.get_errors()
|
|
code_edit.errors = errors
|
|
errors_panel.errors = errors
|
|
parser.free()
|
|
|
|
|
|
func show_build_error_dialog() -> void:
|
|
build_error_dialog.dialog_text = DialogueConstants.translate("errors_with_build")
|
|
build_error_dialog.popup_centered()
|
|
|
|
|
|
# Generate translation line IDs for any line that doesn't already have one
|
|
func generate_translations_keys() -> void:
|
|
randomize()
|
|
seed(Time.get_unix_time_from_system())
|
|
|
|
var parser = DialogueManagerParser.new()
|
|
|
|
var cursor: Vector2 = code_edit.get_cursor()
|
|
var lines: PackedStringArray = code_edit.text.split("\n")
|
|
|
|
var key_regex = RegEx.new()
|
|
key_regex.compile("\\[ID:(?<key>.*?)\\]")
|
|
|
|
# Make list of known keys
|
|
var known_keys = {}
|
|
for i in range(0, lines.size()):
|
|
var line = lines[i]
|
|
var found = key_regex.search(line)
|
|
if found:
|
|
var text = ""
|
|
var l = line.replace(found.strings[0], "").strip_edges().strip_edges()
|
|
if l.begins_with("- "):
|
|
text = parser.extract_response_prompt(l)
|
|
elif ":" in l:
|
|
text = l.split(":")[1]
|
|
else:
|
|
text = l
|
|
known_keys[found.strings[found.names.get("key")]] = text
|
|
|
|
# Add in any that are missing
|
|
for i in lines.size():
|
|
var line = lines[i]
|
|
var l = line.strip_edges()
|
|
|
|
if parser.is_line_empty(l): continue
|
|
if parser.is_condition_line(l, true): continue
|
|
if parser.is_title_line(l): continue
|
|
if parser.is_mutation_line(l): continue
|
|
if parser.is_goto_line(l): continue
|
|
if parser.is_import_line(l): continue
|
|
|
|
if "[ID:" in line: continue
|
|
|
|
var key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10)
|
|
while key in known_keys:
|
|
key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10)
|
|
|
|
var text = ""
|
|
if l.begins_with("- "):
|
|
text = parser.extract_response_prompt(l)
|
|
else:
|
|
text = l.substr(l.find(":") + 1)
|
|
|
|
lines[i] = line.replace(text, text + " [ID:%s]" % key)
|
|
known_keys[key] = text
|
|
|
|
code_edit.text = "\n".join(lines)
|
|
code_edit.set_cursor(cursor)
|
|
_on_code_edit_text_changed()
|
|
|
|
parser.free()
|
|
|
|
|
|
# Add a translation file to the project settings
|
|
func add_path_to_project_translations(path: String) -> void:
|
|
var translations: PackedStringArray = ProjectSettings.get_setting("internationalization/locale/translations")
|
|
if not path in translations:
|
|
translations.append(path)
|
|
ProjectSettings.save()
|
|
|
|
|
|
# Export dialogue and responses to CSV
|
|
func export_translations_to_csv(path: String) -> void:
|
|
var file: FileAccess
|
|
|
|
# If the file exists, open it first and work out which keys are already in it
|
|
var existing_csv = {}
|
|
var commas = []
|
|
if FileAccess.file_exists(path):
|
|
file = FileAccess.open(path, FileAccess.READ)
|
|
var is_first_line = true
|
|
var line: Array
|
|
while !file.eof_reached():
|
|
line = file.get_csv_line()
|
|
if is_first_line:
|
|
is_first_line = false
|
|
for i in range(2, line.size()):
|
|
commas.append("")
|
|
# Make sure the line isn't empty before adding it
|
|
if line.size() > 0 and line[0].strip_edges() != "":
|
|
existing_csv[line[0]] = line
|
|
|
|
# Start a new file
|
|
file = FileAccess.open(path, FileAccess.WRITE)
|
|
|
|
if not file.file_exists(path):
|
|
file.store_csv_line(["keys", "en"])
|
|
|
|
# Write our translations to file
|
|
var known_keys: PackedStringArray = []
|
|
|
|
var dialogue: Dictionary = DialogueManagerParser.parse_string(code_edit.text, current_file_path).lines
|
|
|
|
# Make a list of stuff that needs to go into the file
|
|
var lines_to_save = []
|
|
for key in dialogue.keys():
|
|
var line: Dictionary = dialogue.get(key)
|
|
|
|
if not line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue
|
|
if line.translation_key in known_keys: continue
|
|
|
|
known_keys.append(line.translation_key)
|
|
|
|
if existing_csv.has(line.translation_key):
|
|
var existing_line = existing_csv.get(line.translation_key)
|
|
existing_line[1] = line.text
|
|
lines_to_save.append(existing_line)
|
|
existing_csv.erase(line.translation_key)
|
|
else:
|
|
lines_to_save.append(PackedStringArray([line.translation_key, line.text] + commas))
|
|
|
|
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
|
for line in existing_csv.values():
|
|
file.store_csv_line(line)
|
|
for line in lines_to_save:
|
|
file.store_csv_line(line)
|
|
|
|
file.close()
|
|
|
|
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
|
editor_plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path)
|
|
|
|
# Add it to the project l10n settings if it's not already there
|
|
var translation_path: String = path.replace(".csv", ".en.translation")
|
|
call_deferred("add_path_to_project_translations", translation_path)
|
|
|
|
|
|
func export_character_names_to_csv(path: String) -> void:
|
|
var file: FileAccess
|
|
|
|
# If the file exists, open it first and work out which keys are already in it
|
|
var existing_csv = {}
|
|
var commas = []
|
|
if FileAccess.file_exists(path):
|
|
file = FileAccess.open(path, FileAccess.READ)
|
|
var is_first_line = true
|
|
var line: Array
|
|
while !file.eof_reached():
|
|
line = file.get_csv_line()
|
|
if is_first_line:
|
|
is_first_line = false
|
|
for i in range(2, line.size()):
|
|
commas.append("")
|
|
# Make sure the line isn't empty before adding it
|
|
if line.size() > 0 and line[0].strip_edges() != "":
|
|
existing_csv[line[0]] = line
|
|
|
|
# Start a new file
|
|
file = FileAccess.open(path, FileAccess.WRITE)
|
|
|
|
if not file.file_exists(path):
|
|
file.store_csv_line(["keys", "en"])
|
|
|
|
# Write our translations to file
|
|
var known_keys: PackedStringArray = []
|
|
|
|
var character_names: PackedStringArray = DialogueManagerParser.parse_string(code_edit.text, current_file_path).character_names
|
|
|
|
# Make a list of stuff that needs to go into the file
|
|
var lines_to_save = []
|
|
for character_name in character_names:
|
|
if character_name in known_keys: continue
|
|
|
|
known_keys.append(character_name)
|
|
|
|
if existing_csv.has(character_name):
|
|
var existing_line = existing_csv.get(character_name)
|
|
existing_line[1] = character_name
|
|
lines_to_save.append(existing_line)
|
|
existing_csv.erase(character_name)
|
|
else:
|
|
lines_to_save.append(PackedStringArray([character_name, character_name] + commas))
|
|
|
|
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
|
for line in existing_csv.values():
|
|
file.store_csv_line(line)
|
|
for line in lines_to_save:
|
|
file.store_csv_line(line)
|
|
|
|
file.close()
|
|
|
|
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
|
editor_plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path)
|
|
|
|
# Add it to the project l10n settings if it's not already there
|
|
var translation_path: String = path.replace(".csv", ".en.translation")
|
|
call_deferred("add_path_to_project_translations", translation_path)
|
|
|
|
|
|
# Import changes back from an exported CSV by matching translation keys
|
|
func import_translations_from_csv(path: String) -> void:
|
|
var cursor: Vector2 = code_edit.get_cursor()
|
|
|
|
if not FileAccess.file_exists(path): return
|
|
|
|
# Open the CSV file and build a dictionary of the known keys
|
|
var keys: Dictionary = {}
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
|
var csv_line: Array
|
|
while !file.eof_reached():
|
|
csv_line = file.get_csv_line()
|
|
if csv_line.size() > 1:
|
|
keys[csv_line[0]] = csv_line[1]
|
|
|
|
var parser: DialogueManagerParser = DialogueManagerParser.new()
|
|
|
|
# Now look over each line in the dialogue and replace the content for matched keys
|
|
var lines: PackedStringArray = code_edit.text.split("\n")
|
|
var start_index: int = 0
|
|
var end_index: int = 0
|
|
for i in range(0, lines.size()):
|
|
var line: String = lines[i]
|
|
var translation_key: String = parser.extract_translation(line)
|
|
if keys.has(translation_key):
|
|
if parser.is_dialogue_line(line):
|
|
start_index = 0
|
|
# See if we need to skip over a character name
|
|
line = line.replace("\\:", "!ESCAPED_COLON!")
|
|
if ": " in line:
|
|
start_index = line.find(": ") + 2
|
|
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":")
|
|
|
|
elif parser.is_response_line(line):
|
|
start_index = line.find("- ") + 2
|
|
# See if we need to skip over a character name
|
|
line = line.replace("\\:", "!ESCAPED_COLON!")
|
|
if ": " in line:
|
|
start_index = line.find(": ") + 2
|
|
end_index = line.length()
|
|
if " =>" in line:
|
|
end_index = line.find(" =>")
|
|
if " [if " in line:
|
|
end_index = line.find(" [if ")
|
|
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":")
|
|
|
|
code_edit.text = "\n".join(lines)
|
|
code_edit.set_cursor(cursor)
|
|
|
|
parser.free()
|
|
|
|
|
|
### Signals
|
|
|
|
|
|
func _on_editor_settings_changed() -> void:
|
|
var editor_settings: EditorSettings = editor_plugin.get_editor_interface().get_editor_settings()
|
|
code_edit.minimap_draw = editor_settings.get_setting("text_editor/appearance/minimap/show_minimap")
|
|
code_edit.minimap_width = editor_settings.get_setting("text_editor/appearance/minimap/minimap_width")
|
|
|
|
|
|
func _on_open_menu_id_pressed(id: int) -> void:
|
|
match id:
|
|
OPEN_OPEN:
|
|
open_dialog.popup_centered()
|
|
OPEN_CLEAR:
|
|
DialogueSettings.clear_recent_files()
|
|
build_open_menu()
|
|
_:
|
|
var menu = open_button.get_popup()
|
|
var item = menu.get_item_text(menu.get_item_index(id))
|
|
open_file(item)
|
|
|
|
|
|
func _on_files_list_file_selected(file_path: String) -> void:
|
|
self.current_file_path = file_path
|
|
|
|
|
|
func _on_insert_button_menu_id_pressed(id: int) -> void:
|
|
match id:
|
|
0:
|
|
code_edit.insert_bbcode("[wave amp=25 freq=5]", "[/wave]")
|
|
1:
|
|
code_edit.insert_bbcode("[shake rate=20 level=10]", "[/shake]")
|
|
3:
|
|
code_edit.insert_bbcode("[wait=1]")
|
|
4:
|
|
code_edit.insert_bbcode("[speed=0.2]")
|
|
5:
|
|
code_edit.insert_bbcode("[next=auto]")
|
|
6:
|
|
code_edit.insert_text("~ title")
|
|
7:
|
|
code_edit.insert_text("Nathan: This is Some Dialogue")
|
|
8:
|
|
code_edit.insert_text("Nathan: Choose a Response...\n- Option 1\n\tNathan: You chose option 1\n- Option 2\n\tNathan: You chose option 2")
|
|
9:
|
|
code_edit.insert_text("% Nathan: This is random line 1.\n% Nathan: This is random line 2.\n%1 Nathan: This is weighted random line 3.")
|
|
10:
|
|
code_edit.insert_text("Nathan: [[Hi|Hello|Howdy]]")
|
|
11:
|
|
code_edit.insert_text("=> title")
|
|
12:
|
|
code_edit.insert_text("=> END")
|
|
|
|
|
|
func _on_translations_button_menu_id_pressed(id: int) -> void:
|
|
match id:
|
|
TRANSLATIONS_GENERATE_LINE_IDS:
|
|
generate_translations_keys()
|
|
|
|
TRANSLATIONS_SAVE_CHARACTERS_TO_CSV:
|
|
translation_source = TranslationSource.CharacterNames
|
|
export_dialog.filters = PackedStringArray(["*.csv ; Translation CSV"])
|
|
export_dialog.current_path = get_last_export_path("csv")
|
|
export_dialog.popup_centered()
|
|
|
|
TRANSLATIONS_SAVE_TO_CSV:
|
|
translation_source = TranslationSource.Lines
|
|
export_dialog.filters = PackedStringArray(["*.csv ; Translation CSV"])
|
|
export_dialog.current_path = get_last_export_path("csv")
|
|
export_dialog.popup_centered()
|
|
|
|
TRANSLATIONS_IMPORT_FROM_CSV:
|
|
import_dialog.current_path = get_last_export_path("csv")
|
|
import_dialog.popup_centered()
|
|
|
|
|
|
func _on_export_dialog_file_selected(path: String) -> void:
|
|
DialogueSettings.set_user_value("last_export_path", path.get_base_dir())
|
|
match path.get_extension():
|
|
"csv":
|
|
match translation_source:
|
|
TranslationSource.CharacterNames:
|
|
export_character_names_to_csv(path)
|
|
TranslationSource.Lines:
|
|
export_translations_to_csv(path)
|
|
|
|
|
|
func _on_import_dialog_file_selected(path: String) -> void:
|
|
DialogueSettings.set_user_value("last_export_path", path.get_base_dir())
|
|
import_translations_from_csv(path)
|
|
|
|
|
|
func _on_main_view_theme_changed():
|
|
apply_theme()
|
|
|
|
|
|
func _on_main_view_visibility_changed() -> void:
|
|
if visible and is_instance_valid(code_edit):
|
|
code_edit.grab_focus()
|
|
|
|
|
|
func _on_new_button_pressed() -> void:
|
|
new_dialog.current_file = ""
|
|
new_dialog.popup_centered()
|
|
|
|
|
|
func _on_new_dialog_file_selected(path: String) -> void:
|
|
new_file(path)
|
|
open_file(path)
|
|
|
|
|
|
func _on_save_dialog_file_selected(path: String) -> void:
|
|
new_file(path, code_edit.text)
|
|
open_file(path)
|
|
|
|
|
|
func _on_open_button_about_to_popup() -> void:
|
|
build_open_menu()
|
|
|
|
|
|
func _on_open_dialog_file_selected(path: String) -> void:
|
|
open_file(path)
|
|
|
|
|
|
func _on_save_all_button_pressed() -> void:
|
|
save_files()
|
|
|
|
|
|
func _on_code_edit_text_changed() -> void:
|
|
title_list.titles = code_edit.get_titles()
|
|
|
|
var buffer = open_buffers[current_file_path]
|
|
buffer.text = code_edit.text
|
|
files_list.mark_file_as_unsaved(current_file_path, buffer.text != buffer.pristine_text)
|
|
save_all_button.disabled = open_buffers.values().filter(func(d): return d.text != d.pristine_text).size() == 0
|
|
|
|
parse_timer.start(1)
|
|
|
|
|
|
func _on_code_edit_active_title_change(title: String) -> void:
|
|
title_list.select_title(title)
|
|
DialogueSettings.set_user_value("run_title", title)
|
|
|
|
|
|
func _on_code_edit_caret_changed() -> void:
|
|
DialogueSettings.set_caret(current_file_path, code_edit.get_cursor())
|
|
|
|
|
|
func _on_code_edit_error_clicked(line_number: int) -> void:
|
|
errors_panel.show_error_for_line_number(line_number)
|
|
|
|
|
|
func _on_title_list_title_selected(title: String) -> void:
|
|
code_edit.go_to_title(title)
|
|
code_edit.grab_focus()
|
|
|
|
|
|
func _on_parse_timer_timeout() -> void:
|
|
parse_timer.stop()
|
|
parse()
|
|
|
|
|
|
func _on_errors_panel_error_pressed(line_number: int, column_number: int) -> void:
|
|
code_edit.set_caret_line(line_number)
|
|
code_edit.set_caret_column(column_number)
|
|
code_edit.grab_focus()
|
|
|
|
|
|
func _on_search_button_toggled(button_pressed: bool) -> void:
|
|
if code_edit.last_selected_text:
|
|
search_and_replace.input.text = code_edit.last_selected_text
|
|
|
|
search_and_replace.visible = button_pressed
|
|
|
|
|
|
func _on_search_and_replace_open_requested() -> void:
|
|
search_button.set_pressed_no_signal(true)
|
|
search_and_replace.visible = true
|
|
|
|
|
|
func _on_search_and_replace_close_requested() -> void:
|
|
search_button.set_pressed_no_signal(false)
|
|
search_and_replace.visible = false
|
|
code_edit.grab_focus()
|
|
|
|
|
|
func _on_settings_button_pressed() -> void:
|
|
settings_dialog.popup_centered()
|
|
|
|
|
|
func _on_settings_view_script_button_pressed(path: String) -> void:
|
|
settings_dialog.hide()
|
|
editor_plugin.get_editor_interface().edit_resource(load(path))
|
|
|
|
|
|
func _on_test_button_pressed() -> void:
|
|
apply_changes()
|
|
|
|
if errors_panel.errors.size() > 0:
|
|
errors_dialog.popup_centered()
|
|
return
|
|
|
|
DialogueSettings.set_user_value("is_running_test_scene", true)
|
|
DialogueSettings.set_user_value("run_resource_path", current_file_path)
|
|
var test_scene_path: String = DialogueSettings.get_setting("custom_test_scene_path", "res://addons/dialogue_manager/test_scene.tscn")
|
|
editor_plugin.get_editor_interface().play_custom_scene(test_scene_path)
|
|
|
|
|
|
func _on_settings_dialog_confirmed() -> void:
|
|
parse()
|
|
code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE
|
|
code_edit.grab_focus()
|
|
|
|
|
|
func _on_docs_button_pressed() -> void:
|
|
OS.shell_open("https://github.com/nathanhoad/godot_dialogue_manager")
|
|
|
|
|
|
func _on_files_list_file_popup_menu_requested(at_position: Vector2) -> void:
|
|
files_popup_menu.position = Vector2(get_viewport().position) + files_list.global_position + at_position
|
|
files_popup_menu.popup()
|
|
|
|
|
|
func _on_files_popup_menu_about_to_popup() -> void:
|
|
files_popup_menu.clear()
|
|
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.save"), ITEM_SAVE, KEY_MASK_CTRL | KEY_MASK_ALT | KEY_S)
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.save_as"), ITEM_SAVE_AS)
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.close"), ITEM_CLOSE, KEY_MASK_CTRL | KEY_W)
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.close_all"), ITEM_CLOSE_ALL)
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.close_other_files"), ITEM_CLOSE_OTHERS)
|
|
files_popup_menu.add_separator()
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.copy_file_path"), ITEM_COPY_PATH)
|
|
files_popup_menu.add_item(DialogueConstants.translate("buffer.show_in_filesystem"), ITEM_SHOW_IN_FILESYSTEM)
|
|
|
|
|
|
func _on_files_popup_menu_id_pressed(id: int) -> void:
|
|
match id:
|
|
ITEM_SAVE:
|
|
save_file(current_file_path)
|
|
ITEM_SAVE_AS:
|
|
save_dialog.popup_centered()
|
|
ITEM_CLOSE:
|
|
close_file(current_file_path)
|
|
ITEM_CLOSE_ALL:
|
|
for path in open_buffers.keys():
|
|
close_file(path)
|
|
ITEM_CLOSE_OTHERS:
|
|
for path in open_buffers.keys():
|
|
if path != current_file_path:
|
|
close_file(path)
|
|
|
|
ITEM_COPY_PATH:
|
|
DisplayServer.clipboard_set(current_file_path)
|
|
ITEM_SHOW_IN_FILESYSTEM:
|
|
show_file_in_filesystem(current_file_path)
|
|
|
|
|
|
func _on_code_edit_external_file_requested(path: String, title: String) -> void:
|
|
open_file(path)
|
|
if title != "":
|
|
code_edit.go_to_title(title)
|
|
else:
|
|
code_edit.set_caret_line(0)
|
|
|
|
|
|
func _on_close_confirmation_dialog_confirmed() -> void:
|
|
save_file(current_file_path)
|
|
remove_file_from_open_buffers(current_file_path)
|
|
|
|
|
|
func _on_close_confirmation_dialog_custom_action(action: StringName) -> void:
|
|
if action == "discard":
|
|
remove_file_from_open_buffers(current_file_path)
|
|
close_confirmation_dialog.hide()
|