Skip to content

Commit

Permalink
[license] Add file system watcher
Browse files Browse the repository at this point in the history
  • Loading branch information
IceflowRE committed Dec 11, 2023
1 parent 9df71de commit 908c164
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 95 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ Group them into categories, add descriptions or web links.

The data is stored inside a json file. This file is automatically added to the export, you do not need to add it yourself. If you provide license files instead of a text, they are also exported.

If you add paths to license data, it will be automatically adjusted if you rename a file or folder inside the editor.

You can change the project license file either with a button at the upper right, in the license menu. Or inside the project settings under the menu `Plugins` -> `Licenses`.

### Compatibility
Expand Down Expand Up @@ -245,6 +247,10 @@ License class.

### Changelog

#### 1.7.0

- Add renaming of paths when a file or folder gets renamed inside the editor.

#### 1.6.1

- Workaround show engine components, calling static function is bugged
Expand Down
25 changes: 25 additions & 0 deletions addons/licenses/component.gd
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ class License:
self.web = data.get("web", "")
return self

func duplicate():
var dup = new()
dup.name = self.name
dup.identifier = self.identifier
dup.text = self.text
dup.file = self.file
dup.web = self.web

return dup

## Identifier
var id: String
## Use to structure the licenses to top categories. E.g. Textures, Fonts, ...
Expand Down Expand Up @@ -145,3 +155,18 @@ func deserialize(data: Dictionary):
for license: Dictionary in data.get("licenses", []):
self.licenses.append(License.new().deserialize(license))
return self

func duplicate():
var dup = new()
dup.id = self.id
dup.category = self.category
dup.name = self.name
dup.version = self.version
dup.copyright = self.copyright.duplicate()
dup.contact = self.contact
dup.web = self.web
dup.paths = self.paths.duplicate()
for license in self.licenses:
dup.licenses.append(license.duplicate())

return dup
39 changes: 39 additions & 0 deletions addons/licenses/internal/components_container.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
extends Node

const Component := preload("../component.gd")
const Licenses := preload("../licenses.gd")

signal components_changed()

var _components: Array[Component] = []

func emit_changed() -> void:
self.components_changed.emit()

func add_component(component: Component) -> void:
self._components.append(component)
self._components.sort_custom(Licenses.compare_components_ascending)

func set_components(components_: Array[Component]) -> void:
self._components = components_

func components() -> Array[Component]:
return self._components

func get_at(idx: int) -> Component:
return self._components[idx]

func remove_at(idx: int) -> void:
self._components.remove_at(idx)

func remove_component(component: Component) -> void:
var idx: int = self._components.find(component)
if idx == -1:
return
self.remove_at(idx)

func sort_custom(fn: Callable) -> void:
self._components.sort_custom(fn)

func count() -> int:
return len(self._components)
106 changes: 30 additions & 76 deletions addons/licenses/internal/components_tree.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,18 @@
extends Tree

const Component := preload("../component.gd")
const ComponentDetailTree := preload("component_detail_tree.gd")
const BaseHandler := preload("handler/base.gd")
const ObjectHandler := preload("handler/object.gd")
const ArrayHandler := preload("handler/array.gd")
const StringHandler := preload("handler/string.gd")
const StringFileHandler := preload("handler/string_file.gd")
const StringMultiLineHandler := preload("handler/string_multiline.gd")
const ComponentsContainer := preload("components_container.gd")
const Licenses := preload("../licenses.gd")

const _BTN_ID_REMOVE: int = 2

signal components_changed()

@export_node_path("Tree") var _component_detail_path; @onready var _component_detail: ComponentDetailTree = self.get_node(_component_detail_path)
signal component_selected(comp: Component)
signal component_remove(comp: Component)
signal component_add(comp: Component)

var show_readonly_components: bool = false:
set = set_show_readonly_components
var _components: Array[Component] = []
var _components: ComponentsContainer
## cached value
var _readonly_components: Array[Component] = []

Expand All @@ -29,32 +23,14 @@ func set_show_readonly_components(show: bool) -> void:
self._readonly_components = Licenses.get_required_engine_components()
else:
self._readonly_components = []

func _ready() -> void:
self._component_detail.component_edited.connect(self._on_component_edited)
self._component_detail.handlers = [ObjectHandler, StringFileHandler, StringMultiLineHandler, StringHandler, ArrayHandler]

func add_component(component: Component) -> void:
self._components.append(component)
self._components.sort_custom(Licenses.compare_components_ascending)
self.reload()

# will not emit components_changed signal
func set_components(components_: Array[Component]) -> void:
self._components = components_
self.reload()

func get_components() -> Array[Component]:
return self._components

# also reloads the view
# do not use internally
func select_component(component: Component) -> void:
self._component_detail.set_component(component)
self.reload()
func _ready() -> void:
self.allow_rmb_select = true

func get_selected_component() -> Component:
return self._component_detail.get_component()
# only used once
func set_components(comp: ComponentsContainer) -> void:
self._components = comp

func _create_category_item(category_cache: Dictionary, category: String, root: TreeItem) -> TreeItem:
if category in category_cache:
Expand Down Expand Up @@ -82,11 +58,7 @@ func _add_tree_item(component: Component, idx: int, parent: TreeItem) -> TreeIte
item.set_tooltip_text(0, tooltip)
return item

func _add_component(component: Component, category_cache: Dictionary, root: TreeItem, idx: int) -> TreeItem:
var parent: TreeItem = self._create_category_item(category_cache, component.category, root)
return self._add_tree_item(component, idx, parent)

func reload() -> void:
func reload(scroll_to: Component = null) -> void:
self.clear()
if self.get_root() != null:
push_error("could not clear")
Expand All @@ -99,23 +71,25 @@ func reload() -> void:
# count current added custom components
var idx: int = 0

while idx < len(self._components) or readonly_idx < len(self._readonly_components):
while idx < self._components.count() or readonly_idx < len(self._readonly_components):
var component: Component = null
var cur_idx: int = 0
var cmp: bool = false
if idx < len(self._components) and readonly_idx < len(self._readonly_components):
cmp = Licenses.compare_components_ascending(self._components[idx], self._readonly_components[readonly_idx])
# compare readonly items with editable, to determine which one to show first
if idx < self._components.count() and readonly_idx < len(self._readonly_components):
cmp = Licenses.compare_components_ascending(self._components.get_at(idx), self._readonly_components[readonly_idx])
if readonly_idx >= len(self._readonly_components) or cmp:
component = self._components[idx]
component = self._components.get_at(idx)
cur_idx = idx
idx = idx + 1
elif idx >= len(self._components) or not cmp:
elif idx >= self._components.count() or not cmp:
component = self._readonly_components[readonly_idx]
cur_idx = readonly_idx
readonly_idx = readonly_idx + 1

var item: TreeItem = self._add_component(component, category_cache, root, cur_idx)
if component == self._component_detail.get_component():
var parent: TreeItem = self._create_category_item(category_cache, component.category, root)
var item: TreeItem = self._add_tree_item(component, cur_idx, parent)
if scroll_to != null && component == scroll_to:
self.scroll_to_item(item)
item.select(0)

Expand All @@ -127,7 +101,7 @@ func _get_drag_data(at_position: Vector2) -> Variant:
var item: TreeItem = self.get_item_at_position(at_position)
if item == null:
return null
if not item.has_meta("idx") or item.get_meta("readonly"):
if not item.has_meta("idx") or (item.get_meta("readonly") as bool):
return null

self.set_drop_mode_flags(Tree.DROP_MODE_INBETWEEN)
Expand All @@ -145,61 +119,41 @@ func _drop_data(at_position: Vector2, data: Variant) -> void:
var category: String = ""
# below or above item with category
if to_item.has_meta("idx"):
category = self._components[to_item.get_meta("idx")].category
category = self._components.get_at(to_item.get_meta("idx") as int).category
# below category node
elif shift == 1:
category = to_item.get_text(0)
var cur_component: Component = self._components[data.get_meta("idx")]
var cur_component: Component = self._components.get_at(data.get_meta("idx") as int)
if cur_component.category == category:
return
cur_component.category = category
self._components.sort_custom(Licenses.compare_components_ascending)
self.components_changed.emit()
self.reload()
self._component_detail.reload()
self._components.emit_changed()

func _on_item_selected() -> void:
var item: TreeItem = self.get_selected()
if not item.has_meta("readonly"):
return
var component: Component
if item.get_meta("readonly"):
if item.get_meta("readonly") as bool:
component = self._readonly_components[item.get_meta("idx")]
else:
component = self._components[item.get_meta("idx")]
self._component_detail.set_component(component)
component = self._components.get_at(item.get_meta("idx") as int)
self.component_selected.emit(component)

func _on_button_clicked(item: TreeItem, column: int, id: int, mouse_button_idx: int) -> void:
match id:
_BTN_ID_REMOVE:
var comp: Component = self._components[item.get_meta("idx")]
self._components.remove_at(item.get_meta("idx"))
self._components.sort_custom(Licenses.compare_components_ascending)
self.components_changed.emit()
# refresh detail view if the current component was removed
if comp == self._component_detail.get_component():
self._component_detail.set_component(null)
self.reload()

# callback from commponent detail tree
func _on_component_edited(component: Component) -> void:
self._components.sort_custom(Licenses.compare_components_ascending)
self.components_changed.emit()
self.reload()
self.component_remove.emit(self._components.get_at(item.get_meta("idx") as int))

func _on_item_edited() -> void:
var category_item: TreeItem = self.get_edited()
var old_category: String = category_item.get_meta("category")
var new_category: String = category_item.get_text(0)
category_item.set_meta("category", new_category)
for component: Component in self._components:
for component: Component in self._components.components():
if component.category == old_category:
component.category = new_category

self._components.sort_custom(Licenses.compare_components_ascending)
self.components_changed.emit()
# we cannot reload the tree while it is processing any kind of input/signals
# https://github.com/godotengine/godot/issues/50084
call_deferred("reload")
# reload detail view as the category can be changed
self._component_detail.reload()
self._components.emit_changed()
36 changes: 36 additions & 0 deletions addons/licenses/internal/file_system_watcher.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
extends RefCounted

const ComponentsContainer := preload("components_container.gd")

var _components: ComponentsContainer

func _init(components: ComponentsContainer):
self._components = components
var fs_dock: FileSystemDock = EditorInterface.get_file_system_dock()
fs_dock.files_moved.connect(self._on_file_moved)
fs_dock.folder_moved.connect(self._on_folder_moved)

func _on_file_moved(old_file: String, new_file: String) -> void:
var changed: bool = false
for comp in self._components.components():
for idx in range(comp.paths.size()):
var path: String = comp.paths[idx]
if path == old_file:
changed = true
comp.paths[idx] = new_file
if changed:
self._components.emit_changed()

func _on_folder_moved(old_folder: String, new_folder: String) -> void:
var old_folder_no_slash: String = old_folder.rstrip("/")
var changed: bool = false
for comp in self._components.components():
for idx in range(comp.paths.size()):
var path: String = comp.paths[idx]
if path == old_folder_no_slash:
changed = true
comp.paths[idx] = new_folder.rstrip("/")
if path.begins_with(old_folder):
comp.paths[idx] = new_folder.rstrip("/") + "/" + comp.paths[idx].trim_prefix(old_folder)
if changed:
self._components.emit_changed()
Loading

0 comments on commit 908c164

Please sign in to comment.