MusicTimer/addons/kanban_tasks/plugin.gd
2024-03-22 17:11:54 +01:00

442 lines
12 KiB
GDScript

@tool
extends "standalone_plugin.gd"
const __Singletons := preload("./plugin_singleton/singletons.gd")
const __Shortcuts := preload("./view/shortcuts.gd")
const __EditContext := preload("./view/edit_context.gd")
const __Settings := preload("./data/settings.gd")
const __BoardData := preload("./data/board.gd")
const __LayoutData := preload("./data/layout.gd")
const __TaskData := preload("./data/task.gd")
const __CategoryData := preload("./data/category.gd")
const __StageData := preload("./data/stage.gd")
const __BoardView := preload("./view/board/board.tscn")
const __BoardViewType := preload("./view/board/board.gd")
const __StartView := preload("./view/start/start.tscn")
const __StartViewType := preload("./view/start/start.gd")
const __DocumentationView := preload("./view/documentation/documentation.tscn")
const SETTINGS_KEY: String = "kanban_tasks/general/settings"
enum {
ACTION_SAVE,
ACTION_SAVE_AS,
ACTION_OPEN,
ACTION_CREATE,
ACTION_CLOSE,
ACTION_DOCUMENTATION,
ACTION_QUIT,
}
var main_panel_frame: MarginContainer
var start_view: __StartViewType
var file_dialog_save: FileDialog
var file_dialog_open: FileDialog
var discard_changes_dialog: ConfirmationDialog
var documentation_dialog: AcceptDialog
var file_menu: PopupMenu
var help_menu: PopupMenu
var board_view: __BoardViewType
var board_label: Label
var board_path: String = "":
set(value):
board_path = value
__update_board_label()
__update_menus()
var board_changed: bool = false:
set(value):
board_changed = value
__update_board_label()
func _enter_tree() -> void:
board_label = Label.new()
if not Engine.is_editor_hint():
add_control_to_container(CONTAINER_TOOLBAR, board_label)
file_menu = PopupMenu.new()
file_menu.name = "File"
file_menu.add_item("Save board", ACTION_SAVE)
file_menu.add_item("Save board as...", ACTION_SAVE_AS)
file_menu.add_item("Close board", ACTION_CLOSE)
file_menu.add_item("Open board...", ACTION_OPEN)
file_menu.add_item("Create board", ACTION_CREATE)
file_menu.add_separator()
file_menu.add_item("Quit", ACTION_QUIT)
file_menu.id_pressed.connect(__action)
add_menu(file_menu)
help_menu = PopupMenu.new()
help_menu.name = "Help"
help_menu.add_item("Documentation", ACTION_DOCUMENTATION)
help_menu.id_pressed.connect(__action)
add_menu(help_menu)
file_dialog_save = FileDialog.new()
file_dialog_save.access = FileDialog.ACCESS_FILESYSTEM
file_dialog_save.file_mode = FileDialog.FILE_MODE_SAVE_FILE
file_dialog_save.add_filter("*.kanban, *.json", "Kanban Board")
file_dialog_save.min_size = Vector2(800, 500)
file_dialog_save.file_selected.connect(__save_board)
get_editor_interface().get_base_control().add_child(file_dialog_save)
file_dialog_open = FileDialog.new()
file_dialog_open.access = FileDialog.ACCESS_FILESYSTEM
file_dialog_open.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog_open.add_filter("*.kanban, *.json", "Kanban Board")
file_dialog_open.min_size = Vector2(800, 500)
file_dialog_open.file_selected.connect(__open_board)
get_editor_interface().get_base_control().add_child(file_dialog_open)
discard_changes_dialog = ConfirmationDialog.new()
discard_changes_dialog.dialog_text = "All unsaved changes will be discarded."
discard_changes_dialog.unresizable = true
get_editor_interface().get_base_control().add_child(discard_changes_dialog)
documentation_dialog = __DocumentationView.instantiate()
get_editor_interface().get_base_control().add_child(documentation_dialog)
main_panel_frame = MarginContainer.new()
main_panel_frame.add_theme_constant_override(&"margin_top", 5)
main_panel_frame.add_theme_constant_override(&"margin_left", 5)
main_panel_frame.add_theme_constant_override(&"margin_bottom", 5)
main_panel_frame.add_theme_constant_override(&"margin_right", 5)
main_panel_frame.size_flags_vertical = Control.SIZE_EXPAND_FILL
get_editor_interface().get_editor_main_screen().add_child(main_panel_frame)
start_view = __StartView.instantiate()
start_view.create_board.connect(__action.bind(ACTION_CREATE))
start_view.open_board.connect(__on_start_view_open_board)
main_panel_frame.add_child(start_view)
_make_visible(false)
await get_tree().create_timer(0.0).timeout
__load_settings()
if Engine.is_editor_hint():
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var editor_data_file_path = ctx.settings.editor_data_file_path
if FileAccess.file_exists(editor_data_file_path):
__open_board(editor_data_file_path)
elif FileAccess.file_exists("res://addons/kanban_tasks/data.json"):
# TODO: Remove sometime in the future.
# Migrate from old version.
__open_board("res://addons/kanban_tasks/data.json")
__save_board(editor_data_file_path)
else:
__create_board()
__save_board(editor_data_file_path)
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.save_board.connect(__editor_save_board)
ctx.reload_board.connect(__editor_reload_board)
ctx.create_board.connect(__editor_create_board)
__update_menus()
func _exit_tree() -> void:
if not Engine.is_editor_hint():
remove_control_from_container(CONTAINER_TOOLBAR, board_label)
board_label.queue_free()
remove_menu(file_menu)
file_menu.queue_free()
remove_menu(help_menu)
file_menu.queue_free()
file_dialog_save.queue_free()
file_dialog_open.queue_free()
discard_changes_dialog.queue_free()
documentation_dialog.queue_free()
main_panel_frame.queue_free()
start_view.queue_free()
if is_instance_valid(board_view):
board_view.queue_free()
func _shortcut_input(event: InputEvent) -> void:
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
if not Engine.is_editor_hint() and shortcuts.save.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTION_SAVE)
if not Engine.is_editor_hint() and shortcuts.save_as.matches_event(event):
get_viewport().set_input_as_handled()
__action(ACTION_SAVE_AS)
func _has_main_screen() -> bool:
return true
func _make_visible(visible) -> void:
if main_panel_frame:
main_panel_frame.visible = visible
func _get_plugin_name() -> String:
return "Tasks"
func _get_plugin_icon() -> Texture2D:
return preload("./icon.svg")
func _notification(what: int) -> void:
match what:
NOTIFICATION_WM_CLOSE_REQUEST:
if not Engine.is_editor_hint():
if not board_changed:
get_tree().quit()
else:
__request_discard_changes(get_tree().quit)
func __update_menus() -> void:
if not is_instance_valid(file_menu):
return
var shortcuts: __Shortcuts = __Singletons.instance_of(__Shortcuts, self)
file_menu.set_item_disabled(
file_menu.get_item_index(ACTION_SAVE),
not is_instance_valid(board_view),
)
file_menu.set_item_shortcut(
file_menu.get_item_index(ACTION_SAVE),
shortcuts.save,
)
file_menu.set_item_disabled(
file_menu.get_item_index(ACTION_SAVE_AS),
not is_instance_valid(board_view),
)
file_menu.set_item_shortcut(
file_menu.get_item_index(ACTION_SAVE_AS),
shortcuts.save_as,
)
file_menu.set_item_disabled(
file_menu.get_item_index(ACTION_CLOSE),
not is_instance_valid(board_view),
)
func __update_board_label() -> void:
if not is_instance_valid(board_label):
return
if is_instance_valid(board_view):
if board_path.is_empty():
board_label.text = "unsaved"
else:
board_label.text = board_path
if board_changed:
board_label.text += "*"
else:
board_label.text = ""
func __action(id: int) -> void:
match id:
ACTION_SAVE:
__request_save()
ACTION_SAVE_AS:
__request_save(true)
ACTION_CREATE:
if not board_changed:
__create_board()
else:
__request_discard_changes(__create_board)
ACTION_OPEN:
if not board_changed:
file_dialog_open.popup_centered()
else:
__request_discard_changes(file_dialog_open.popup_centered)
ACTION_CLOSE:
if not board_changed:
__close_board()
else:
__request_discard_changes(__close_board)
ACTION_DOCUMENTATION:
documentation_dialog.popup_centered()
ACTION_QUIT:
get_tree().get_root().propagate_notification(NOTIFICATION_WM_CLOSE_REQUEST)
func __editor_save_board() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
__save_board(ctx.settings.editor_data_file_path)
func __editor_reload_board() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
__action(ACTION_SAVE)
__open_board(ctx.settings.editor_data_file_path)
func __editor_create_board() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
__action(ACTION_SAVE)
__create_board()
__save_board(ctx.settings.editor_data_file_path)
func __request_discard_changes(callback: Callable) -> void:
for connection in discard_changes_dialog.confirmed.get_connections():
discard_changes_dialog.confirmed.disconnect(connection["callable"])
discard_changes_dialog.confirmed.connect(callback)
discard_changes_dialog.popup_centered()
func __request_save(force_new_location: bool = false) -> void:
if not is_instance_valid(board_view):
return
if not force_new_location and not board_path.is_empty():
__save_board(board_path)
else:
file_dialog_save.popup_centered()
func __create_board() -> void:
var data := __BoardData.new()
data.layout = __LayoutData.new([
PackedStringArray([data.add_stage(__StageData.new("Todo"))]),
PackedStringArray([data.add_stage(__StageData.new("Doing"))]),
PackedStringArray([data.add_stage(__StageData.new("Done"))]),
])
data.add_category(
__CategoryData.new(
"Task",
get_editor_interface().get_base_control().
get_theme_color(&"accent_color", &"Editor")
)
)
data.changed.connect(__on_board_changed)
__make_board_view_visible(data)
board_path = ""
board_changed = false
func __save_board(path: String) -> void:
if is_instance_valid(board_view):
__add_to_recent_files(path)
board_path = path
board_view.board_data.save(path)
board_changed = false
func __open_board(path: String) -> void:
var data := __BoardData.new()
data.load(path)
data.changed.connect(__on_board_changed)
__make_board_view_visible(data)
__add_to_recent_files(path)
board_path = path
board_changed = false
func __close_board() -> void:
board_view.queue_free()
board_view = null
board_path = ""
board_changed = false
start_view.show()
func __add_to_recent_files(path: String) -> void:
if Engine.is_editor_hint():
return
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var files = ctx.settings.recent_files
if path in files:
files.remove_at(files.find(path))
files.insert(0, path)
else:
files.insert(0, path)
files.resize(ctx.settings.recent_file_count)
ctx.settings.recent_files = files
func __on_board_changed() -> void:
board_changed = true
if Engine.is_editor_hint():
__request_save()
func __on_start_view_open_board(path: String) -> void:
if path.is_empty():
__action(ACTION_OPEN)
else:
__open_board(path)
func __make_board_view_visible(data: __BoardData) -> void:
if is_instance_valid(board_view):
board_view.queue_free()
board_view = __BoardView.instantiate()
board_view.show_documentation.connect(__action.bind(ACTION_DOCUMENTATION))
board_view.board_data = data
main_panel_frame.add_child(board_view)
start_view.hide()
func __save_settings() -> void:
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
var data := JSON.stringify(ctx.settings.to_json())
if Engine.is_editor_hint():
get_editor_interface().get_editor_settings().set_setting(
SETTINGS_KEY,
data,
)
else:
ProjectSettings.set_setting(
SETTINGS_KEY,
data,
)
save_project_settings()
func __load_settings() -> void:
var data: String = "{}"
if Engine.is_editor_hint():
var editor_settings = get_editor_interface().get_editor_settings()
if editor_settings.has_setting(SETTINGS_KEY):
data = editor_settings.get_setting(SETTINGS_KEY)
else:
if ProjectSettings.has_setting(SETTINGS_KEY):
data = ProjectSettings.get_setting(SETTINGS_KEY)
var json = JSON.new()
var err = json.parse(data)
if err != OK:
push_error(
"Error "
+ str(err)
+ " while parsing settings. At line "
+ str(json.get_error_line())
+ " the following problem occured:\n"
+ json.get_error_message()
)
return
var ctx: __EditContext = __Singletons.instance_of(__EditContext, self)
ctx.settings.from_json(json.data)
ctx.settings.changed.connect(__save_settings)