tasks list working

This commit is contained in:
xananax prozaxx 2023-03-10 03:25:50 +04:00
parent 66b25bae97
commit 24c4b98a5e
10 changed files with 317 additions and 104 deletions

57
assets/stop_small.svg Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="stop_small.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#323232"
bordercolor="#111111"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="px"
showgrid="true"
inkscape:zoom="35.356609"
inkscape:cx="12.402208"
inkscape:cy="11.115319"
inkscape:window-width="1896"
inkscape:window-height="1029"
inkscape:window-x="12"
inkscape:window-y="39"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid7036"
originx="0"
originy="0"
empspacing="8" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="opacity:1;fill:#ffffff;fill-opacity:0.994836;stroke-width:4;stroke-linecap:round;stroke-opacity:0;paint-order:stroke markers fill;stop-color:#000000"
id="rect341"
width="4.7624998"
height="4.7624998"
x="0.52916664"
y="0.5291667" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,35 @@
[remap]
importer="texture"
type="StreamTexture"
path="res://.import/stop_small.svg-d7819f3a82ff689d7390798487abb123.stex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/stop_small.svg"
dest_files=[ "res://.import/stop_small.svg-d7819f3a82ff689d7390798487abb123.stex" ]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_mode=0
compress/bptc_ldr=0
compress/normal_map=0
flags/repeat=0
flags/filter=true
flags/mipmaps=false
flags/anisotropic=false
flags/srgb=2
process/fix_alpha_border=true
process/premult_alpha=false
process/HDR_as_SRGB=false
process/invert_color=false
process/normal_map_invert_y=false
stream=false
size_limit=0
detect_3d=true
svg/scale=0.6

View File

@ -40,6 +40,11 @@ _global_script_classes=[ {
"path": "res://scripts/time_entry.gd" "path": "res://scripts/time_entry.gd"
}, { }, {
"base": "Reference", "base": "Reference",
"class": "TimeEntryTreeItem",
"language": "GDScript",
"path": "res://scripts/time_entry_tree_item.gd"
}, {
"base": "Reference",
"class": "TimeSheet", "class": "TimeSheet",
"language": "GDScript", "language": "GDScript",
"path": "res://scripts/time_sheet.gd" "path": "res://scripts/time_sheet.gd"
@ -56,6 +61,7 @@ _global_script_class_icons={
"FileWatcher": "", "FileWatcher": "",
"TimeEntriesItemsTree": "", "TimeEntriesItemsTree": "",
"TimeEntry": "", "TimeEntry": "",
"TimeEntryTreeItem": "",
"TimeSheet": "", "TimeSheet": "",
"TimeStamp": "" "TimeStamp": ""
} }

View File

@ -1,16 +1,16 @@
class_name ConfigManager extends Resource class_name ConfigManager extends Resource
signal entry_started
signal entry_stopped
signal file_changed signal file_changed
signal time_sheet_reloaded
const CONFIG_PATH := "user://settings.cfg" const CONFIG_PATH := "user://settings.cfg"
var _config := ConfigFile.new()
var _watcher: FileWatcher
var timesheet: TimeSheet setget ,get_timesheet var timesheet: TimeSheet setget ,get_timesheet
var _config := ConfigFile.new()
var _watcher: FileWatcher
func get_timesheet() -> TimeSheet: func get_timesheet() -> TimeSheet:
if timesheet == null: if timesheet == null:
timesheet = _load_timesheet(get_current_file()) timesheet = _load_timesheet(get_current_file())
@ -20,29 +20,26 @@ func get_timesheet() -> TimeSheet:
func _load_timesheet(path: String) -> TimeSheet: func _load_timesheet(path: String) -> TimeSheet:
timesheet = TimeSheet.restore(path) timesheet = TimeSheet.restore(path)
if timesheet == null: if timesheet == null:
_watcher = null
return null return null
_watcher = FileWatcher.new() _watcher = FileWatcher.new()
_watcher.file_name = path _watcher.file_name = path
# warning-ignore:return_value_discarded
_watcher.connect("file_changed", self, "reload_timesheet")
_watcher.start() _watcher.start()
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
_watcher.connect("file_changed", self, "_on_file_changed") #timesheet.connect("entry_started", self, "_on_entry_started")
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
timesheet.connect("entry_started", self, "_on_entry_started") #timesheet.connect("entry_stopped", self, "_on_entry_stopped")
# warning-ignore:return_value_discarded
timesheet.connect("entry_stopped", self, "_on_entry_stopped")
return timesheet return timesheet
func _on_entry_started() -> void: func reload_timesheet() -> void:
emit_signal("entry_started") var new_timesheet = _load_timesheet(get_current_file())
if new_timesheet == null:
func _on_entry_stopped() -> void: printerr("failed to load new timesheet")
emit_signal("entry_stopped") return
timesheet = new_timesheet
emit_signal("time_sheet_reloaded")
func _on_file_changed() -> void:
emit_signal("file_changed")
var current_file: String = "" setget set_current_file, get_current_file var current_file: String = "" setget set_current_file, get_current_file

View File

@ -2,11 +2,14 @@
## Has a beginning and an end ## Has a beginning and an end
class_name TimeEntry class_name TimeEntry
signal end_time_updated
signal closed
var name := "" var name := ""
var closed := false var is_closed := false
var start_time := TimeStamp.new() var start_time := TimeStamp.new()
var end_time := TimeStamp.new() var end_time := TimeStamp.new()
var previous_total := 0
func start_recording() -> TimeEntry: func start_recording() -> TimeEntry:
@ -17,6 +20,13 @@ func start_recording() -> TimeEntry:
func update() -> void: func update() -> void:
end_time = end_time.from_current_time() end_time = end_time.from_current_time()
emit_signal("end_time_updated")
func close() -> void:
update()
is_closed = true
emit_signal("closed")
func get_elapsed_seconds() -> int: func get_elapsed_seconds() -> int:
@ -24,21 +34,11 @@ func get_elapsed_seconds() -> int:
return elapsed return elapsed
func get_total_elapsed_seconds() -> int:
var elapsed := get_elapsed_seconds() + previous_total
return elapsed
func get_period() -> String: func get_period() -> String:
var time_in_secs := get_elapsed_seconds() var time_in_secs := get_elapsed_seconds()
return time_to_period(time_in_secs) return time_to_period(time_in_secs)
func get_total_period() -> String:
var time_in_secs := get_total_elapsed_seconds()
return time_to_period(time_in_secs)
static func time_to_period(time_in_secs: int) -> String: static func time_to_period(time_in_secs: int) -> String:
# warning-ignore:integer_division # warning-ignore:integer_division
var seconds := time_in_secs%60 var seconds := time_in_secs%60
@ -48,14 +48,16 @@ static func time_to_period(time_in_secs: int) -> String:
var hours := (time_in_secs/60)/60 var hours := (time_in_secs/60)/60
return "%02d:%02d:%02d" % [hours, minutes, seconds] return "%02d:%02d:%02d" % [hours, minutes, seconds]
func to_csv_line() -> PoolStringArray: func to_csv_line() -> PoolStringArray:
return PoolStringArray([ return PoolStringArray([
name, name,
start_time, start_time,
end_time, end_time,
str(get_elapsed_seconds()) if closed else tr(Consts.ONGOING) str(get_elapsed_seconds()) if is_closed else tr(Consts.ONGOING)
]) ])
static func is_csv_line_valid(line: PoolStringArray) -> bool: static func is_csv_line_valid(line: PoolStringArray) -> bool:
return line.size() > 3 return line.size() > 3
@ -68,9 +70,9 @@ func from_csv_line(line: PoolStringArray) -> TimeEntry:
start_time.from_string(start_time_string) start_time.from_string(start_time_string)
var elapsed_seconds = int(line[3]) if line[3].is_valid_integer() else 0 var elapsed_seconds = int(line[3]) if line[3].is_valid_integer() else 0
closed = elapsed_seconds > 0 is_closed = elapsed_seconds > 0
if closed == true: if is_closed == true:
var end_time_string = line[2] var end_time_string = line[2]
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
end_time.from_string(end_time_string) end_time.from_string(end_time_string)
@ -79,5 +81,12 @@ func from_csv_line(line: PoolStringArray) -> TimeEntry:
end_time.from_current_time() end_time.from_current_time()
return self return self
func to_dict() -> Dictionary:
return {
start_time = start_time,
closed = is_closed,
}
func _to_string() -> String: func _to_string() -> String:
return "%s\t%s\t%s"%[name, Consts.ONGOING if closed == false else "", start_time] return "%s\t%s\t%s"%[name, tr(Consts.ONGOING) if is_closed == false else end_time.to_string(), start_time]

View File

@ -0,0 +1,70 @@
class_name TimeEntryTreeItem
signal end_time_updated
var time_entry: TimeEntry
var children := {}
var time_entries := []
func get_child(parts: Array, or_create := false):
var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
if parts.size() == 0:
return self
var part: String = parts.pop_front()
if not children.has(part):
if or_create == false:
return null
var time_entry_tree_item = TimeEntryTreeItem.new()
# warning-ignore:return_value_discarded
time_entry_tree_item.connect("end_time_updated", self, "_on_end_time_updated")
children[part] = time_entry_tree_item
return children[part].get_child(parts, or_create)
func append(new_time_entry: TimeEntry) -> void:
var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
var time_entry_tree_item = TimeEntryTreeItem.new()
time_entry_tree_item.time_entry = new_time_entry
# warning-ignore:return_value_discarded
new_time_entry.connect("end_time_updated", time_entry_tree_item, "_on_end_time_updated")
# warning-ignore:return_value_discarded
time_entry_tree_item.connect("end_time_updated", self, "_on_end_time_updated")
time_entries.append(time_entry_tree_item)
func _on_end_time_updated() -> void:
emit_signal("end_time_updated")
func get_elapsed_seconds() -> int:
var seconds := time_entry.get_elapsed_seconds() if time_entry != null else 0
for child_name in children:
var child = children[child_name]
seconds += child.get_elapsed_seconds()
for entry in time_entries:
seconds += entry.get_elapsed_seconds()
return seconds
func get_period() -> String:
var time_in_secs := get_elapsed_seconds()
return TimeEntry.time_to_period(time_in_secs)
func to_dict() -> Dictionary:
var json := {}
var times := []
if time_entries.size() > 0:
for entry in time_entries:
times.append(entry.to_dict())
json["__time"] = times
if children.size() > 0:
for name in children:
json[name] = children[name].to_dict()
return json
func _to_string() -> String:
var json := to_dict()
var json_string := JSON.print(json, "\t")
return json_string

View File

@ -1,13 +1,9 @@
class_name TimeSheet class_name TimeSheet
signal entry_started
signal entry_stopped
var source_path := "" var source_path := ""
var entries := [] var entries := []
var entries_names := {} var tree := TimeEntryTreeItem.new()
var current_entry: TimeEntry
## Loads the data file ## Loads the data file
func load_file() -> bool: func load_file() -> bool:
@ -19,6 +15,7 @@ func load_file() -> bool:
printerr("Failed to open file %s"%[ProjectSettings.globalize_path(source_path)]) printerr("Failed to open file %s"%[ProjectSettings.globalize_path(source_path)])
return false return false
return true return true
while not file.eof_reached(): while not file.eof_reached():
var line := file.get_csv_line() var line := file.get_csv_line()
if line.size() == 0 or "".join(line).length() == 0: if line.size() == 0 or "".join(line).length() == 0:
@ -28,22 +25,32 @@ func load_file() -> bool:
continue continue
var entry := TimeEntry.new().from_csv_line(line) var entry := TimeEntry.new().from_csv_line(line)
entries.append(entry) entries.append(entry)
if entry.closed == false: # warning-ignore:return_value_discarded
current_entry = entry entry.connect("closed", self, "save")
if not entries_names.has(entry.name):
entries_names[entry.name] = 0
entries_names[entry.name] += entry.get_elapsed_seconds()
file.close() file.close()
entries.sort_custom(self, "_sort_entries")
for entry_index in entries.size():
var entry: TimeEntry = entries[entry_index]
var parts: PoolStringArray = entry.name.split("/")
var repo: TimeEntryTreeItem = tree.get_child(parts, true)
repo.append(entry)
return true return true
func _sort_entries(a: TimeEntry, b: TimeEntry) -> bool:
return a.name < b.name
## Adds a new time entry to the tree and to the data file ## Adds a new time entry to the tree and to the data file
func start_entry(entry_name: String) -> void: func start_entry(entry_name: String) -> void:
current_entry = TimeEntry.new().start_recording() var current_entry := TimeEntry.new().start_recording()
current_entry.name = entry_name current_entry.name = entry_name
current_entry.closed = false current_entry.closed = false
if entry_name in entries_names:
current_entry.previous_total = entries_names[entry_name]
var file := File.new() var file := File.new()
var success := file.open(source_path, File.READ_WRITE) var success := file.open(source_path, File.READ_WRITE)
if success != OK: if success != OK:
@ -55,22 +62,10 @@ func start_entry(entry_name: String) -> void:
func update() -> void: func update() -> void:
current_entry.update() for entry in entries:
entries_names[current_entry.name] = current_entry.get_total_elapsed_seconds() var time_entry := entry as TimeEntry
if time_entry.is_closed == false:
time_entry.update()
func close_entry() -> void:
current_entry.closed = true
save()
emit_signal("entry_stopped")
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: func save() -> void:
@ -90,5 +85,3 @@ static func restore(file_path: String) -> TimeSheet:
if success: if success:
return timesheet return timesheet
return null return null

6
test.gd Normal file
View File

@ -0,0 +1,6 @@
tool
extends EditorScript
func _run():
print ("c" < "b")

View File

@ -7,55 +7,91 @@ enum COL{
var config: ConfigManager = preload("res://config_manager.tres") var config: ConfigManager = preload("res://config_manager.tres")
var current_item: TreeItem var _timer := Timer.new()
func _ready() -> void: func _ready() -> void:
hide_root = true
add_child(_timer)
# warning-ignore:return_value_discarded
connect("button_pressed", self, "_on_button_pressed")
# warning-ignore:return_value_discarded
_timer.connect("timeout", self, "_on_timer_timeout")
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
config.connect("file_changed", self, "populate_entries") config.connect("file_changed", self, "populate_entries")
# warning-ignore:return_value_discarded
config.connect("time_sheet_reloaded", self, "populate_entries")
populate_entries() populate_entries()
func populate_entries() -> void: func populate_entries() -> void:
clear() clear()
var _root := create_item() var tree_items_root := create_item()
var entries_names := config.timesheet.entries_names var item_entries_tree := config.timesheet.tree
for entry_name in entries_names: _populate_from_entry(tree_items_root, item_entries_tree)
_timer.start()
#for item in entries:
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
append_name_to_tree(entry_name, entries_names[entry_name]) # set_entry(entry)
# warning-ignore:unused_argument
func set_time_elapsed(total_elapsed: int) -> void: func set_time_elapsed(total_elapsed: int) -> void:
current_item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed)) #current_item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed))
current_item.set_metadata(COL.TIME, total_elapsed) #current_item.set_metadata(COL.TIME, total_elapsed)
pass
func set_current_item(task_name: String) -> void: func set_current_item(_task_name: String) -> void:
current_item = append_name_to_tree(task_name, 0) #current_item = append_name_to_tree(task_name, 0)
pass
func _on_timer_timeout() -> void:
func get_current_text() -> String: config.timesheet.update()
var item := get_selected()
if not item:
return ""
var resp = item.get_metadata(COL.TEXT)
if resp is String:
return resp
return ""
## Adds a new item to the tree, or returns the old item if it exists func _populate_from_entry(tree_item_root: TreeItem, time_entry_item_root: TimeEntryTreeItem):
func append_name_to_tree(task_name: String, total_elapsed := 0) -> TreeItem: var children := time_entry_item_root.children
var item := get_root() for time_entry_name in children:
for item_name in task_name.split("/"): var time_entry_item: TimeEntryTreeItem = children[time_entry_name]
item.collapsed = false var item := find_or_create_item(tree_item_root, time_entry_name)
item = find_item(item, item_name, true) item.set_metadata(COL.TEXT, time_entry_item)
item.set_metadata(COL.TEXT, task_name) item.set_text(COL.TIME, time_entry_item.get_period())
item.set_text(COL.TIME, TimeEntry.time_to_period(total_elapsed)) # warning-ignore:return_value_discarded
item.set_metadata(COL.TIME, total_elapsed) time_entry_item.connect("end_time_updated", self, "_on_time_entry_changed_update_item", [time_entry_item, item])
scroll_to_item(item) _populate_from_entry(item, time_entry_item)
return item var entries = time_entry_item_root.time_entries
for entry_item in entries:
var time_entry_item := entry_item as TimeEntryTreeItem
var item := create_item(tree_item_root)
var time_entry := time_entry_item.time_entry
item.set_text(COL.TEXT, time_entry.start_time.to_string())
item.set_metadata(COL.TEXT, time_entry_item)
item.set_text(COL.TIME, time_entry_item.get_period())
if time_entry.is_closed == false:
var texture := preload("res://assets/stop_small.svg")
item.add_button(COL.TIME, texture)
# warning-ignore:return_value_discarded
time_entry_item.connect("end_time_updated", self, "_on_time_entry_changed_update_item", [time_entry_item, item])
func _on_time_entry_changed_update_item(time_entry_item: TimeEntryTreeItem, item: TreeItem) -> void:
item.set_text(COL.TIME, time_entry_item.get_period())
func _on_button_pressed(item: TreeItem, _column: int, _id: int) -> void:
var time_entry_tree_item: TimeEntryTreeItem = item.get_metadata(COL.TEXT)
if time_entry_tree_item == null:
return
var time_entry := time_entry_tree_item.time_entry
if time_entry == null:
return
if time_entry.is_closed:
return
else:
time_entry.close()
item.erase_button(COL.TIME, 0)
## Unecessary in Godot 4, can bre replaced with get_children() ## Unecessary in Godot 4, can bre replaced with get_children()
@ -73,13 +109,17 @@ static func _get_tree_item_children(item: TreeItem):
## Finds an item in the tree by text ## Finds an item in the tree by text
func find_item(root: TreeItem, item_name: String, or_create: bool) -> TreeItem: func find_item(root: TreeItem, item_name: String) -> TreeItem:
for child in _get_tree_item_children(root): for child in _get_tree_item_children(root):
if child.get_text(COL.TEXT) == item_name: if child.get_text(COL.TEXT) == item_name:
return child return child
if or_create:
var child := create_item(root)
child.set_text(COL.TEXT, item_name)
child.set_text(COL.TIME, Consts.NO_TIME)
return child
return null return null
func find_or_create_item(root: TreeItem, item_name: String) -> TreeItem:
var child := find_item(root, item_name)
if child != null:
return child
child = create_item(root)
child.set_text(COL.TEXT, item_name)
return child

View File

@ -14,7 +14,7 @@ onready var audio_stream_player := $"%AudioStreamPlayer" as AudioStreamPlayer
func _ready() -> void: func _ready() -> void:
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
timer.connect("timeout", self, "_on_timer_timeout") timer.connect("timeout", self, "_on_timer_timeout")
start_button.tooltip_text = tr(Consts.START) start_button.hint_tooltip = tr(Consts.START)
start_button.toggle_mode = true start_button.toggle_mode = true
# warning-ignore:return_value_discarded # warning-ignore:return_value_discarded
start_button.connect("toggled", self, "_on_start_button_toggled") start_button.connect("toggled", self, "_on_start_button_toggled")
@ -57,7 +57,7 @@ func _on_task_name_line_edit_text_changed(new_text: String) -> void:
func set_button_as_stopped() -> void: func set_button_as_stopped() -> void:
start_button.set_pressed_no_signal(false) start_button.set_pressed_no_signal(false)
start_button.tooltip_text = tr(Consts.START) start_button.hint_tooltip = tr(Consts.START)
time_label.text = Consts.NO_TIME time_label.text = Consts.NO_TIME
start_button.theme_type_variation = Consts.THEME_OVERRIDE_START start_button.theme_type_variation = Consts.THEME_OVERRIDE_START
timer.stop() timer.stop()
@ -67,7 +67,7 @@ func set_button_as_started() -> void:
## TODO: make this independent ## TODO: make this independent
# time_entries_items_tree.set_current_item(config.timesheet.current_entry.name) # time_entries_items_tree.set_current_item(config.timesheet.current_entry.name)
start_button.set_pressed_no_signal(true) start_button.set_pressed_no_signal(true)
start_button.tooltip_text = tr(Consts.STOP) start_button.hint_tooltip = tr(Consts.STOP)
start_button.theme_type_variation = Consts.THEME_OVERRIDE_STOP start_button.theme_type_variation = Consts.THEME_OVERRIDE_STOP
timer.start() timer.start()