diff --git a/Main.gd b/Main.gd index d1f070e..edda131 100644 --- a/Main.gd +++ b/Main.gd @@ -1,22 +1,10 @@ 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 time_entries_items_tree: TimeEntriesItemsTree = %TimeEntriesItemsTree @onready var timer: Timer = %Timer @onready var tasks_button: Button = %TasksButton @onready var settings_button: Button = %SettingsButton @@ -33,20 +21,14 @@ const STRINGS := { @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 := "" +var timesheet: TimeSheet +var config: ConfigManager = preload("res://config_manager.tres") 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 config.theme_path: + var new_theme: Theme = ResourceLoader.load(config.theme_path, "Theme") if new_theme != null: theme = new_theme @@ -54,7 +36,7 @@ func _init() -> void: func _ready() -> void: get_tree().set_auto_accept_quit(false) - var _root := previous_tasks_tree.create_item() + var _root := time_entries_items_tree.create_item() file_path_button.pressed.connect( file_path_file_dialog.popup_centered @@ -71,8 +53,7 @@ func _ready() -> 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) + config.theme_path = theme_path ) theme_path_file_dialog.hide() @@ -82,46 +63,37 @@ func _ready() -> void: timer.timeout.connect( func on_timer_timeout() -> void: - current_entry.update() + timesheet.update() - time_label.text = current_entry.get_period() + time_label.text = timesheet.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) + var total_elapsed: int = timesheet.get_total_elapsed_seconds() + time_entries_items_tree.set_time_elapsed(total_elapsed) ) - start_button.tooltip_text = tr(STRINGS.START) + start_button.tooltip_text = tr(Consts.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() + timesheet.start_entry(task_name_line_edit.text) + set_button_as_started() else: - add_to_entries() - start_button.tooltip_text = tr(STRINGS.START) - time_label.text = STRINGS.NO_TIME - timer.stop() + timesheet.close_entry() + set_button_as_stopped() ) - task_name_line_edit.text = config.get_value("MAIN", "last_task_name", "") + task_name_line_edit.text = config.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) + config.last_task_name = new_text ) - previous_tasks_tree.item_selected.connect( + time_entries_items_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) + task_name_line_edit.text = time_entries_items_tree.get_current_text() ) tasks_button.toggle_mode = true @@ -146,10 +118,10 @@ func _ready() -> void: settings_window.hide() ) - sound_check_box.button_pressed = config.get_value("MAIN", "sound_fx", true) + sound_check_box.button_pressed = config.sound_fx_on sound_check_box.toggled.connect( func sound_toggle(is_on: bool) -> void: - config.set_value("MAIN", "sound_fx", is_on) + config.sound_fx_on = is_on ) open_data_dir_button.pressed.connect( @@ -158,208 +130,47 @@ func _ready() -> void: attributions_rich_text_label.meta_clicked.connect(OS.shell_open) - set_current_file(current_file) + set_current_file(config.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 +func set_button_as_started() -> void: + time_entries_items_tree.set_current_item(timesheet.current_entry.name) + start_button.tooltip_text = tr(Consts.STOP) + start_button.theme_type_variation = Consts.THEME_OVERRIDE_STOP + timer.start() -## 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 +func set_button_as_stopped() -> void: + start_button.tooltip_text = tr(Consts.START) + time_label.text = Consts.NO_TIME + start_button.theme_type_variation = Consts.THEME_OVERRIDE_START + timer.stop() ## 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): + timesheet = TimeSheet.restore(new_current_file) + if timesheet == null: 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 + config.current_file = new_current_file + + for entry_name in timesheet.entries_names: + time_entries_items_tree.append_name_to_tree(entry_name, timesheet.entries_names[entry_name]) + + if timesheet.current_entry != null and timesheet.current_entry.closed == false: + start_button.set_pressed_no_signal(true) + set_button_as_started() + file_path_file_dialog.current_path = config.current_file + file_path_file_dialog.current_dir = config.current_file.get_base_dir() + file_path_line_edit.text = config.current_file 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) diff --git a/Main.tscn b/Main.tscn index 1b62a82..5d11ecf 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=6 format=3 uid="uid://bmlciwscreowf"] +[gd_scene load_steps=7 format=3 uid="uid://bmlciwscreowf"] [ext_resource type="Theme" uid="uid://bd8ancgbfsvmd" path="res://assets/default_theme.theme" id="1_1mila"] [ext_resource type="Script" path="res://Main.gd" id="1_vrowr"] [ext_resource type="AudioStream" uid="uid://cdsbhoidgyx70" path="res://assets/pop.ogg" id="3_o37py"] +[ext_resource type="Script" path="res://ui/time_entries_items_tree.gd" id="3_wwscb"] [sub_resource type="InputEventKey" id="InputEventKey_guuii"] device = -1 @@ -125,12 +126,13 @@ theme_override_constants/margin_top = 20 theme_override_constants/margin_right = 20 theme_override_constants/margin_bottom = 20 -[node name="PreviousTasksTree" type="Tree" parent="Main/PreviousTasksWindow/PanelContainer/MarginContainer"] +[node name="TimeEntriesItemsTree" type="Tree" parent="Main/PreviousTasksWindow/PanelContainer/MarginContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 columns = 2 +script = ExtResource("3_wwscb") [node name="SettingsWindow" type="Window" parent="Main"] unique_name_in_owner = true diff --git a/assets/default_theme.theme b/assets/default_theme.theme index 1d4b1cd..7eaebaa 100644 Binary files a/assets/default_theme.theme and b/assets/default_theme.theme differ diff --git a/logo.png b/assets/logo.png similarity index 100% rename from logo.png rename to assets/logo.png diff --git a/logo.png.import b/assets/logo.png.import similarity index 73% rename from logo.png.import rename to assets/logo.png.import index f3ba451..85e368e 100644 --- a/logo.png.import +++ b/assets/logo.png.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://cpmyyivxx0dlt" -path="res://.godot/imported/logo.png-cca8726399059c8d4f806e28e356b14d.ctex" +path="res://.godot/imported/logo.png-e2220799298e3631eb0e245316e0501a.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://logo.png" -dest_files=["res://.godot/imported/logo.png-cca8726399059c8d4f806e28e356b14d.ctex"] +source_file="res://assets/logo.png" +dest_files=["res://.godot/imported/logo.png-e2220799298e3631eb0e245316e0501a.ctex"] [params] diff --git a/logo.svg b/assets/logo.svg similarity index 100% rename from logo.svg rename to assets/logo.svg diff --git a/logo.svg.import b/assets/logo.svg.import similarity index 76% rename from logo.svg.import rename to assets/logo.svg.import index 487d4ef..a36bd31 100644 --- a/logo.svg.import +++ b/assets/logo.svg.import @@ -3,15 +3,15 @@ importer="texture" type="CompressedTexture2D" uid="uid://fk5s6m8qlsei" -path="res://.godot/imported/logo.svg-8d8cf086b974db23ad31f8a2f3ea7d0f.ctex" +path="res://.godot/imported/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://logo.svg" -dest_files=["res://.godot/imported/logo.svg-8d8cf086b974db23ad31f8a2f3ea7d0f.ctex"] +source_file="res://assets/logo.svg" +dest_files=["res://.godot/imported/logo.svg-01597fe4b7eb446be26a49e8a22b6f42.ctex"] [params] diff --git a/assets/stop.svg b/assets/stop.svg new file mode 100644 index 0000000..67f74f0 --- /dev/null +++ b/assets/stop.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/assets/stop.svg.import b/assets/stop.svg.import new file mode 100644 index 0000000..f6849be --- /dev/null +++ b/assets/stop.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c56y8w3k75rxc" +path="res://.godot/imported/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/stop.svg" +dest_files=["res://.godot/imported/stop.svg-fc65124eb2fb3129fbdd4f17f48ea6f3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/config_manager.gd b/config_manager.gd new file mode 100644 index 0000000..896a6e7 --- /dev/null +++ b/config_manager.gd @@ -0,0 +1,51 @@ +class_name ConfigManager extends Resource + + +const CONFIG_PATH := "user://settings.cfg" +var _config := ConfigFile.new() + + +var current_file: String = "": + set(value): + current_file = value + _config.set_value("MAIN", "file", value) + save() + get: + var _default_path := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS, true).path_join("mouse_timer.csv") + return _config.get_value("MAIN", "file", _default_path) + + +var theme_path: String = "": + set(value): + theme_path = value + _config.set_value("MAIN", "theme", value) + save() + get: + return _config.get_value("MAIN", "theme", preload("res://assets/default_theme.theme").resource_path) + + +var last_task_name: String = "": + set(value): + last_task_name = value + _config.set_value("MAIN", "last_task_name", value) + save() + get: + return _config.get_value("MAIN", "last_task_name", "") + + +var sound_fx_on: bool = true: + set(value): + sound_fx_on = value + _config.set_value("MAIN", "sound_fx_on", value) + save() + get: + return _config.get_value("MAIN", "sound_fx", true) + + + +func _init() -> void: + _config.load(CONFIG_PATH) + + +func save() -> void: + _config.save(CONFIG_PATH) diff --git a/config_manager.tres b/config_manager.tres new file mode 100644 index 0000000..84b06f1 --- /dev/null +++ b/config_manager.tres @@ -0,0 +1,6 @@ +[gd_resource type="Resource" script_class="ConfigManager" load_steps=2 format=3 uid="uid://e3yofdasfcli"] + +[ext_resource type="Script" path="res://config_manager.gd" id="1_xfu8y"] + +[resource] +script = ExtResource("1_xfu8y") diff --git a/export_presets.cfg b/export_presets.cfg index 2a88137..154eab7 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -25,7 +25,6 @@ texture_format/bptc=false texture_format/s3tc=true texture_format/etc=false texture_format/etc2=false -texture_format/no_bptc_fallbacks=true binary_format/architecture="x86_64" ssh_remote_deploy/enabled=false ssh_remote_deploy/host="user@host_ip" @@ -50,7 +49,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="exports/Rat Times.app" +export_path="exports/Rat Times.zip" encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false @@ -67,7 +66,7 @@ application/icon="res://logo.icns" application/icon_interpolation=4 application/bundle_identifier="org.mutnt.io.rat-times" application/signature="" -application/app_category="Productivity" +application/app_category="Developer-tools" application/short_version="1.0" application/version="1.0" application/copyright="" @@ -361,7 +360,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="" +export_path="exports/rat-times.ipa" encryption_include_filters="" encryption_exclude_filters="" encrypt_pck=false @@ -454,7 +453,6 @@ texture_format/bptc=false texture_format/s3tc=true texture_format/etc=false texture_format/etc2=false -texture_format/no_bptc_fallbacks=true binary_format/architecture="x86_64" codesign/enable=false codesign/identity_type=0 diff --git a/project.godot b/project.godot index a62fe9c..5cad5e3 100644 --- a/project.godot +++ b/project.godot @@ -16,9 +16,10 @@ run/main_scene="res://Main.tscn" config/use_custom_user_dir=true config/custom_user_dir_name="rat_times" config/features=PackedStringArray("4.0") +run/low_processor_mode=true boot_splash/bg_color=Color(0.141176, 0.141176, 0.141176, 0) boot_splash/show_image=false -config/icon="res://logo.svg" +config/icon="res://assets/logo.svg" config/macos_native_icon="res://logo.icns" config/windows_native_icon="res://logo.ico" diff --git a/scripts/consts.gd b/scripts/consts.gd new file mode 100644 index 0000000..e319f59 --- /dev/null +++ b/scripts/consts.gd @@ -0,0 +1,8 @@ +class_name Consts + +const START := "start" +const STOP := "stop" +const NO_TIME := "00:00:00" +const ONGOING := "ongoing" +const THEME_OVERRIDE_START := "play_button" +const THEME_OVERRIDE_STOP := "stop_button" diff --git a/scripts/time_entry.gd b/scripts/time_entry.gd new file mode 100644 index 0000000..099fac9 --- /dev/null +++ b/scripts/time_entry.gd @@ -0,0 +1,77 @@ +## Describes a row in a timesheet +## Has a beginning and an end +class_name TimeEntry + +var name := "" +var closed := false +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()) if closed else tr(Consts.ONGOING) + ]) + +static func is_csv_line_valid(line: PackedStringArray) -> bool: + return line.size() > 3 + + +func from_csv_line(line: PackedStringArray) -> TimeEntry: + name = line[0] + + var start_time_string = line[1] + start_time.from_string(start_time_string) + + var elapsed_seconds = int(line[3]) if line[3].is_valid_int() else 0 + closed = elapsed_seconds > 0 + + if closed == true: + var end_time_string = line[2] + end_time.from_string(end_time_string) + else: + end_time.from_current_time() + return self + diff --git a/scripts/time_sheet.gd b/scripts/time_sheet.gd new file mode 100644 index 0000000..26ccd5f --- /dev/null +++ b/scripts/time_sheet.gd @@ -0,0 +1,84 @@ +class_name TimeSheet + + +var source_path := "" +var entries: Array[TimeEntry] = [] +var entries_names := {} +var current_entry: TimeEntry + +## Loads the data file +func load_file() -> 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 + 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) + entries.append(entry) + if entry.closed == false: + current_entry = entry + if not entries_names.has(entry.name): + entries_names[entry.name] = 0 + entries_names[entry.name] += entry.get_elapsed_seconds() + file.close() + return true + + +## Adds a new time entry to the tree and to the data file +func start_entry(entry_name: String) -> void: + current_entry = TimeEntry.new().start_recording() + current_entry.name = entry_name + current_entry.closed = false + if entry_name in entries_names: + current_entry.previous_total = entries_names[entry_name] + var file := FileAccess.open(source_path, FileAccess.READ_WRITE) + if file == null: + printerr("Could not open file") + entries.append(current_entry) + file.store_csv_line(current_entry.to_csv_line()) + + +func update() -> void: + current_entry.update() + entries_names[current_entry.name] = current_entry.get_total_elapsed_seconds() + + +func close_entry() -> void: + current_entry.closed = true + save() + + +func get_period() -> String: + return current_entry.get_period() + + +func get_total_elapsed_seconds() -> int: + return current_entry.get_total_elapsed_seconds() + + +func save() -> void: + var file := FileAccess.open(source_path, FileAccess.WRITE) + if file == null: + printerr("Could not open file") + for time_entry in entries: + file.store_csv_line(time_entry.to_csv_line()) + + +static func restore(file_path: String) -> TimeSheet: + var timesheet := TimeSheet.new() + timesheet.source_path = file_path + var success := timesheet.load_file() + if success: + return timesheet + return null + + diff --git a/scripts/time_stamp.gd b/scripts/time_stamp.gd new file mode 100644 index 0000000..53f53eb --- /dev/null +++ b/scripts/time_stamp.gd @@ -0,0 +1,54 @@ +## A simple proxy for the object returned by Godot's time functions +## Ensures proper typing +class_name 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) diff --git a/ui/time_entries_items_tree.gd b/ui/time_entries_items_tree.gd new file mode 100644 index 0000000..c05f3c7 --- /dev/null +++ b/ui/time_entries_items_tree.gd @@ -0,0 +1,52 @@ +class_name TimeEntriesItemsTree extends Tree + +enum COL{ + TEXT, + TIME +} + +var current_item: TreeItem + + +func set_time_elapsed(total_elapsed: int) -> void: + current_item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed)) + current_item.set_metadata(COL.TIME, total_elapsed) + + +func set_current_item(task_name: String) -> void: + current_item = append_name_to_tree(task_name, 0) + + + +func get_current_text() -> String: + var item := get_selected() + if not item: + return "" + var resp: String = item.get_metadata(COL.TEXT) + return resp + + +## 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 := 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) + 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, Consts.NO_TIME) + return child + return null