366 lines
10 KiB
GDScript3
366 lines
10 KiB
GDScript3
|
extends Control
|
||
|
|
||
|
const CONFIG_PATH := "user://settings.cfg"
|
||
|
|
||
|
enum COL{
|
||
|
TEXT,
|
||
|
TIME
|
||
|
}
|
||
|
|
||
|
const STRINGS := {
|
||
|
START = "start",
|
||
|
STOP = "stop",
|
||
|
NO_TIME = "00:00:00",
|
||
|
}
|
||
|
|
||
|
@onready var time_label: Label = %TimeLabel
|
||
|
@onready var start_button: Button = %StartButton
|
||
|
@onready var task_name_line_edit: LineEdit = %TaskNameLineEdit
|
||
|
@onready var previous_tasks_tree: Tree = %PreviousTasksTree
|
||
|
@onready var timer: Timer = %Timer
|
||
|
@onready var tasks_button: Button = %TasksButton
|
||
|
@onready var settings_button: Button = %SettingsButton
|
||
|
@onready var previous_tasks_window: Window = %PreviousTasksWindow
|
||
|
@onready var settings_window: Window = %SettingsWindow
|
||
|
@onready var file_path_file_dialog: FileDialog = %FilePathFileDialog
|
||
|
@onready var file_path_line_edit: LineEdit = %FilePathLineEdit
|
||
|
@onready var file_path_button: Button = %FilePathButton
|
||
|
@onready var theme_path_file_dialog: FileDialog = %ThemePathFileDialog
|
||
|
@onready var theme_path_button: Button = %ThemePathButton
|
||
|
@onready var sound_check_box: CheckBox = %SoundCheckBox
|
||
|
@onready var attributions_rich_text_label: RichTextLabel = %AttributionsRichTextLabel
|
||
|
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||
|
@onready var open_data_dir_button: Button = %OpenDataDirButton
|
||
|
|
||
|
|
||
|
var previous_entries: Array[TimeEntry] = []
|
||
|
var current_entry := TimeEntry.new()
|
||
|
var current_item: TreeItem
|
||
|
var config := ConfigFile.new()
|
||
|
var current_file := ""
|
||
|
|
||
|
|
||
|
func _init() -> void:
|
||
|
|
||
|
config.load(CONFIG_PATH)
|
||
|
var default_path := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS, true).path_join("mouse_timer.csv")
|
||
|
current_file = config.get_value("MAIN", "file", default_path)
|
||
|
if config.has_section_key("MAIN", "theme"):
|
||
|
var new_theme: Theme = ResourceLoader.load(config.get_value("MAIN", "theme", "res://default_theme.theme"), "Theme")
|
||
|
if new_theme != null:
|
||
|
theme = new_theme
|
||
|
|
||
|
|
||
|
func _ready() -> void:
|
||
|
|
||
|
get_tree().set_auto_accept_quit(false)
|
||
|
var _root := previous_tasks_tree.create_item()
|
||
|
|
||
|
file_path_button.pressed.connect(
|
||
|
file_path_file_dialog.popup_centered
|
||
|
)
|
||
|
|
||
|
file_path_file_dialog.file_selected.connect(set_current_file)
|
||
|
file_path_line_edit.text_submitted.connect(set_current_file)
|
||
|
|
||
|
theme_path_button.pressed.connect(
|
||
|
theme_path_file_dialog.popup_centered
|
||
|
)
|
||
|
theme_path_file_dialog.file_selected.connect(
|
||
|
func theme_selected(theme_path: String) -> void:
|
||
|
var new_theme: Theme = ResourceLoader.load(theme_path, "Theme")
|
||
|
if new_theme != null:
|
||
|
theme = new_theme
|
||
|
config.set_value("MAIN", "theme", theme_path)
|
||
|
config.save(CONFIG_PATH)
|
||
|
)
|
||
|
|
||
|
theme_path_file_dialog.hide()
|
||
|
file_path_file_dialog.hide()
|
||
|
previous_tasks_window.hide()
|
||
|
settings_window.hide()
|
||
|
|
||
|
timer.timeout.connect(
|
||
|
func on_timer_timeout() -> void:
|
||
|
current_entry.update()
|
||
|
|
||
|
time_label.text = current_entry.get_period()
|
||
|
|
||
|
var total_elapsed: int = current_entry.get_total_elapsed_seconds()
|
||
|
current_item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed))
|
||
|
current_item.set_metadata(COL.TIME, total_elapsed)
|
||
|
)
|
||
|
|
||
|
start_button.tooltip_text = tr(STRINGS.START)
|
||
|
start_button.toggle_mode = true
|
||
|
start_button.toggled.connect(
|
||
|
func start(is_on: bool) -> void:
|
||
|
if sound_check_box.button_pressed:
|
||
|
audio_stream_player.play()
|
||
|
if is_on:
|
||
|
current_entry = TimeEntry.new().start_recording()
|
||
|
current_entry.name = task_name_line_edit.text
|
||
|
current_item = append_name_to_tree(task_name_line_edit.text)
|
||
|
current_entry.previous_total = current_item.get_metadata(COL.TIME)
|
||
|
start_button.tooltip_text = tr(STRINGS.STOP)
|
||
|
timer.start()
|
||
|
else:
|
||
|
add_to_entries()
|
||
|
start_button.tooltip_text = tr(STRINGS.START)
|
||
|
time_label.text = STRINGS.NO_TIME
|
||
|
timer.stop()
|
||
|
)
|
||
|
|
||
|
|
||
|
task_name_line_edit.text = config.get_value("MAIN", "last_task_name", "")
|
||
|
task_name_line_edit.text_changed.connect(
|
||
|
func(new_text: String) -> void:
|
||
|
config.set_value("MAIN", "last_task_name", new_text)
|
||
|
config.save(CONFIG_PATH)
|
||
|
)
|
||
|
previous_tasks_tree.item_selected.connect(
|
||
|
func item_selected() -> void:
|
||
|
var item := previous_tasks_tree.get_selected()
|
||
|
task_name_line_edit.text = item.get_metadata(COL.TEXT)
|
||
|
)
|
||
|
|
||
|
tasks_button.toggle_mode = true
|
||
|
tasks_button.toggled.connect(
|
||
|
func tasks_toggled(is_on: bool) -> void:
|
||
|
previous_tasks_window.visible = is_on
|
||
|
)
|
||
|
previous_tasks_window.close_requested.connect(
|
||
|
func close() -> void:
|
||
|
tasks_button.set_pressed_no_signal(false)
|
||
|
previous_tasks_window.hide()
|
||
|
)
|
||
|
|
||
|
settings_button.toggle_mode = true
|
||
|
settings_button.toggled.connect(
|
||
|
func settings_toggled(is_on: bool) -> void:
|
||
|
settings_window.visible = is_on
|
||
|
)
|
||
|
settings_window.close_requested.connect(
|
||
|
func close() -> void:
|
||
|
settings_button.set_pressed_no_signal(false)
|
||
|
settings_window.hide()
|
||
|
)
|
||
|
|
||
|
sound_check_box.button_pressed = config.get_value("MAIN", "sound_fx", true)
|
||
|
sound_check_box.toggled.connect(
|
||
|
func sound_toggle(is_on: bool) -> void:
|
||
|
config.set_value("MAIN", "sound_fx", is_on)
|
||
|
)
|
||
|
|
||
|
open_data_dir_button.pressed.connect(
|
||
|
OS.shell_open.bind(OS.get_user_data_dir())
|
||
|
)
|
||
|
|
||
|
attributions_rich_text_label.meta_clicked.connect(OS.shell_open)
|
||
|
|
||
|
set_current_file(current_file)
|
||
|
|
||
|
|
||
|
## Adds a new time entry to the tree and to the data file
|
||
|
func add_to_entries() -> void:
|
||
|
previous_entries.append(current_entry)
|
||
|
var file := FileAccess.open(current_file, FileAccess.WRITE)
|
||
|
if file == null:
|
||
|
printerr("Could not open file")
|
||
|
file.store_csv_line(current_entry.to_csv_line())
|
||
|
|
||
|
|
||
|
## Adds a new item to the tree, or returns the old item if it exists
|
||
|
func append_name_to_tree(task_name: String, total_time := 0) -> TreeItem:
|
||
|
var item := previous_tasks_tree.get_root()
|
||
|
for item_name in task_name.split("/"):
|
||
|
item.collapsed = false
|
||
|
item = find_item(item, item_name, true)
|
||
|
item.set_metadata(COL.TEXT, task_name)
|
||
|
if not item.get_metadata(COL.TIME):
|
||
|
item.set_metadata(COL.TIME, total_time)
|
||
|
previous_tasks_tree.scroll_to_item(item)
|
||
|
return item
|
||
|
|
||
|
|
||
|
## Finds an item in the tree by text
|
||
|
func find_item(root: TreeItem, item_name: String, or_create: bool) -> TreeItem:
|
||
|
for child in root.get_children():
|
||
|
if child.get_text(COL.TEXT) == item_name:
|
||
|
return child
|
||
|
if or_create:
|
||
|
var child := root.create_child()
|
||
|
child.set_text(COL.TEXT, item_name)
|
||
|
child.set_text(COL.TIME, STRINGS.NO_TIME)
|
||
|
return child
|
||
|
return null
|
||
|
|
||
|
|
||
|
## Changes the data file path, and loads the new path
|
||
|
func set_current_file(new_current_file: String) -> void:
|
||
|
if not load_file(new_current_file):
|
||
|
return
|
||
|
previous_entries.clear()
|
||
|
current_file = new_current_file
|
||
|
config.set_value("MAIN", "file", current_file)
|
||
|
config.save(CONFIG_PATH)
|
||
|
file_path_file_dialog.current_path = current_file
|
||
|
file_path_file_dialog.current_dir = current_file.get_base_dir()
|
||
|
file_path_line_edit.text = current_file
|
||
|
|
||
|
|
||
|
## Loads the data file
|
||
|
func load_file(source_path: String) -> bool:
|
||
|
var file := FileAccess.open(source_path, FileAccess.READ)
|
||
|
if file == null:
|
||
|
file = FileAccess.open(source_path, FileAccess.WRITE)
|
||
|
if file == null:
|
||
|
printerr("Failed to open file %s"%[ProjectSettings.globalize_path(source_path)])
|
||
|
return false
|
||
|
return true
|
||
|
var collector := {}
|
||
|
while not file.eof_reached():
|
||
|
var line := file.get_csv_line()
|
||
|
if line.size() == 0 or "".join(line).length() == 0:
|
||
|
continue
|
||
|
if not TimeEntry.is_csv_line_valid(line):
|
||
|
push_warning("CSV Line `%s` is not conform"%[",".join(line)])
|
||
|
continue
|
||
|
var entry := TimeEntry.new().from_csv_line(line)
|
||
|
previous_entries.append(entry)
|
||
|
if not collector.has(entry.name):
|
||
|
collector[entry.name] = 0
|
||
|
collector[entry.name] += entry.get_elapsed_seconds()
|
||
|
for entry_name in collector:
|
||
|
append_name_to_tree(entry_name, collector[entry_name])
|
||
|
file.close()
|
||
|
return true
|
||
|
|
||
|
|
||
|
|
||
|
func _notification(what: int) -> void:
|
||
|
if what == NOTIFICATION_WM_CLOSE_REQUEST:
|
||
|
if start_button.button_pressed:
|
||
|
add_to_entries()
|
||
|
get_tree().quit()
|
||
|
|
||
|
|
||
|
## Unused; if a manual quit button is added, this would be used
|
||
|
func quit() -> void:
|
||
|
get_tree().notification(NOTIFICATION_WM_CLOSE_REQUEST)
|
||
|
|
||
|
|
||
|
class TimeEntry:
|
||
|
|
||
|
var name := ""
|
||
|
var start_time := TimeStamp.new()
|
||
|
var end_time := TimeStamp.new()
|
||
|
var previous_total := 0
|
||
|
|
||
|
|
||
|
func start_recording() -> TimeEntry:
|
||
|
start_time = start_time.from_current_time()
|
||
|
end_time = end_time.from_current_time()
|
||
|
return self
|
||
|
|
||
|
|
||
|
func update() -> void:
|
||
|
end_time = end_time.from_current_time()
|
||
|
|
||
|
|
||
|
func get_elapsed_seconds() -> int:
|
||
|
var elapsed := end_time.get_difference(start_time)
|
||
|
return elapsed
|
||
|
|
||
|
|
||
|
func get_total_elapsed_seconds() -> int:
|
||
|
var elapsed := get_elapsed_seconds() + previous_total
|
||
|
return elapsed
|
||
|
|
||
|
|
||
|
func get_period() -> String:
|
||
|
var time_in_secs := get_elapsed_seconds()
|
||
|
return TimeEntry.time_to_period(time_in_secs)
|
||
|
|
||
|
|
||
|
func get_total_period() -> String:
|
||
|
var time_in_secs := get_total_elapsed_seconds()
|
||
|
return TimeEntry.time_to_period(time_in_secs)
|
||
|
|
||
|
|
||
|
static func time_to_period(time_in_secs: int) -> String:
|
||
|
var seconds := time_in_secs%60
|
||
|
@warning_ignore("integer_division")
|
||
|
var minutes := (time_in_secs/60)%60
|
||
|
@warning_ignore("integer_division")
|
||
|
var hours := (time_in_secs/60)/60
|
||
|
return "%02d:%02d:%02d" % [hours, minutes, seconds]
|
||
|
|
||
|
func to_csv_line() -> PackedStringArray:
|
||
|
return PackedStringArray([
|
||
|
name,
|
||
|
start_time,
|
||
|
end_time,
|
||
|
str(get_elapsed_seconds()),
|
||
|
])
|
||
|
|
||
|
static func is_csv_line_valid(line: PackedStringArray) -> bool:
|
||
|
return line.size() > 2
|
||
|
|
||
|
|
||
|
func from_csv_line(line: PackedStringArray) -> TimeEntry:
|
||
|
name = line[0]
|
||
|
start_time.from_string(line[1])
|
||
|
end_time.from_string(line[2])
|
||
|
return self
|
||
|
|
||
|
|
||
|
class TimeStamp:
|
||
|
var year := 0
|
||
|
var month := 0
|
||
|
var day := 0
|
||
|
var weekday := 0
|
||
|
var hour := 0
|
||
|
var minute := 0
|
||
|
var second := 0
|
||
|
var unix := 0
|
||
|
|
||
|
|
||
|
func from_current_time() -> TimeStamp:
|
||
|
return from_dict(Time.get_datetime_dict_from_system())
|
||
|
|
||
|
|
||
|
func from_dict(time: Dictionary) -> TimeStamp:
|
||
|
year = time.year
|
||
|
month = time.month
|
||
|
day = time.day
|
||
|
weekday = time.weekday
|
||
|
hour = time.hour
|
||
|
minute = time.minute
|
||
|
second = time.second
|
||
|
unix = Time.get_unix_time_from_datetime_dict(time)
|
||
|
return self
|
||
|
|
||
|
func to_dict() -> Dictionary:
|
||
|
return {
|
||
|
year = year,
|
||
|
month = month,
|
||
|
day = day,
|
||
|
weekday = weekday,
|
||
|
hour = hour,
|
||
|
minute = minute,
|
||
|
second = second,
|
||
|
}
|
||
|
|
||
|
func get_difference(other_timestamp: TimeStamp) -> int:
|
||
|
return unix - other_timestamp.unix
|
||
|
|
||
|
|
||
|
func from_string(time_string: String) -> TimeStamp:
|
||
|
var time := Time.get_datetime_dict_from_datetime_string(time_string, true)
|
||
|
return from_dict(time)
|
||
|
|
||
|
|
||
|
func _to_string() -> String:
|
||
|
return Time.get_datetime_string_from_datetime_dict(to_dict(), false)
|