|
|
|
|
|
function CompareValues(val1, val2) |
|
if not IsT(val1) and not IsT(val2) and type(val1) == "table" and type(val2) == "table" then |
|
local pstr1, pstr2 = pstr("", 1024), pstr("", 1024) |
|
ValueToLuaCode(val1, nil, pstr1) |
|
ValueToLuaCode(val2, nil, pstr2) |
|
return pstr1 == pstr2 |
|
end |
|
return val1 == val2 |
|
end |
|
|
|
function GedPropCapture(obj) |
|
if not IsKindOf(obj, "PropertyObject") or IsKindOf(obj, "CObject") and not IsValid(obj) then |
|
return |
|
end |
|
local prop_capture = {} |
|
for _, prop_meta in ipairs(obj:GetProperties()) do |
|
local no_edit = prop_eval(prop_meta.no_edit, obj, prop_meta) |
|
local read_only = prop_eval(prop_meta.read_only, obj, prop_meta) |
|
local dont_save = prop_eval(prop_meta.dont_save, obj, prop_meta) |
|
if not no_edit and not read_only and not dont_save then |
|
local editor = prop_meta.editor |
|
if editor == "object" or editor == "objects" then |
|
return |
|
end |
|
|
|
local id = prop_meta.id |
|
local value = obj:GetProperty(id) |
|
if not obj:IsDefaultPropertyValue(id, prop_meta, value) then |
|
prop_capture[id] = obj:ClonePropertyValue(value, prop_meta) |
|
end |
|
end |
|
end |
|
return prop_capture |
|
end |
|
|
|
function GedCreatePropValuesUndoFn(obj, old_capture) |
|
local new_capture = GedPropCapture(obj) |
|
if not old_capture or not new_capture then return end |
|
|
|
local changed, nils = {}, {} |
|
for _, key in ipairs(table.union(table.keys(new_capture), table.keys(old_capture))) do |
|
local old_value, new_value = rawget(old_capture, key), rawget(new_capture, key) |
|
if not CompareValues(old_value, new_value) then |
|
changed[key] = old_value |
|
nils[key] = old_value == nil or nil |
|
end |
|
end |
|
|
|
if next(changed) or next(nils) then |
|
return function() |
|
for key, value in pairs(changed) do obj:SetProperty(key, value) end |
|
for key, value in pairs(nils) do obj:SetProperty(key, nil) end |
|
if obj:IsKindOf("Preset") then |
|
obj:SortPresets() |
|
end |
|
ObjModified(obj) |
|
end |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function GedListMultiOp(socket, obj, op, mode, selection, ...) |
|
if not selection or not selection[1] then return end |
|
|
|
table.sort(selection) |
|
local idx1 = mode == "forward" and 1 or #selection |
|
local idx2 = mode == "forward" and #selection or 1 |
|
local step = mode == "forward" and 1 or -1 |
|
|
|
local new_sel, undo_fns = {}, {} |
|
for i = idx1, idx2, step do |
|
local sel, undo_fn = _G[op](socket, obj, selection[i], ...) |
|
if not sel or type(sel) == "string" then |
|
for i = 1, #undo_fns do undo_fns[i]() end |
|
return sel |
|
end |
|
table.insert(undo_fns, 1, undo_fn) |
|
table.insert(new_sel, sel) |
|
end |
|
if mode == "delete" then |
|
new_sel = { new_sel[#new_sel] } |
|
else |
|
table.sort(new_sel) |
|
end |
|
|
|
return new_sel, function() for i = 1, #undo_fns do undo_fns[i]() end ObjModified(obj) end |
|
end |
|
|
|
function GedTreeMultiOp(socket, obj, op, mode, selection, ...) |
|
if not selection or not selection[1] then return end |
|
|
|
local node_path = selection[1] |
|
selection = selection[2] |
|
table.sort(selection) |
|
local idx1 = mode:starts_with("forward") and 1 or #selection |
|
local idx2 = mode:starts_with("forward") and #selection or 1 |
|
local step = mode:starts_with("forward") and 1 or -1 |
|
local shift = 0 |
|
|
|
local new_sel_node |
|
local new_sel, undo_fns = {}, {} |
|
for i = idx1, idx2, step do |
|
local path = table.copy(node_path) |
|
path[#path] = selection[i] - (mode == "forward_inwards" and shift or 0) |
|
if mode == "forward_inwards" then |
|
path.parent_leaf_idx = new_sel_node and new_sel_node[#new_sel_node - 1] |
|
end |
|
local sel, undo_fn = _G[op](socket, obj, path, ...) |
|
if sel == nil or type(sel) == "string" then |
|
for i = 1, #undo_fns do undo_fns[i]() end |
|
return sel |
|
end |
|
table.insert(undo_fns, 1, undo_fn) |
|
new_sel_node = mode ~= "delete" and new_sel_node or sel |
|
if sel then |
|
table.insert(new_sel, sel[#sel] + (mode == "backward_outwards" and shift or 0)) |
|
end |
|
shift = shift + 1 |
|
end |
|
if mode == "delete" then |
|
new_sel = { new_sel[#new_sel] } |
|
else |
|
table.sort(new_sel) |
|
end |
|
|
|
return { new_sel_node, new_sel }, function() for i = 1, #undo_fns do undo_fns[i]() end ObjModified(obj) end |
|
end |
|
|
|
function GedDisplayTempStatus(id, text, delay) |
|
delay = delay or 600 |
|
return GedSetUiStatus(id, text, delay) |
|
end |
|
|
|
|
|
|
|
|
|
if FirstLoad then |
|
GedClipboard = { stored_objs = false, base_class = false, } |
|
GedPropertiesContainer = { data = false } |
|
GedSerializeInProgress = false |
|
end |
|
|
|
|
|
|
|
function GedSerialize(value) |
|
assert(not GedSerializeInProgress) |
|
GedSerializeInProgress = true |
|
local data = ValueToLuaCode(value) |
|
GedSerializeInProgress = false |
|
return data |
|
end |
|
|
|
|
|
function GedCopyToClipboard(obj, base_class, idx_list, message) |
|
table.sort(idx_list) |
|
GedClipboard.base_class = base_class |
|
GedClipboard.mod_ids = {} |
|
local objs = {} |
|
for i = 1, #idx_list do |
|
local item = TreeNodeByPath(obj, idx_list[i]) |
|
table.insert(objs, item) |
|
assert(item:IsKindOf(base_class)) |
|
|
|
table.insert(GedClipboard.mod_ids, item.mod and item.mod.id) |
|
end |
|
GedClipboard.stored_objs = GedSerialize(objs) |
|
|
|
GedDisplayTempStatus("clipboard", string.format("%d %s copied", #idx_list, base_class)) |
|
end |
|
|
|
local function __paste(class_name, ...) |
|
local class = g_Classes[class_name] |
|
return class:HasMember("__paste") and class:__paste(...) or class:__fromluacode(...) |
|
end |
|
|
|
function GedPasteObjCode(code, error_text) |
|
local old_place_obj = PlaceObj |
|
PlaceObj = __paste |
|
local err, objs = LuaCodeToTuple(code, _G) |
|
PlaceObj = old_place_obj |
|
if err then |
|
print(error_text, err) |
|
return |
|
end |
|
GenerateLocalizationIDs(objs) |
|
return objs |
|
end |
|
|
|
function GedRestoreFromClipboard(base_class) |
|
local clipboard_class = rawget(_G, GedClipboard.base_class) |
|
if not clipboard_class or base_class and not clipboard_class:IsKindOf(base_class) then |
|
return |
|
end |
|
return GedPasteObjCode(GedClipboard.stored_objs, "Ged: Error restoring object") |
|
end |
|
|
|
function GedDuplicateObjects(obj, idx_list) |
|
table.sort(idx_list) |
|
local objs = {} |
|
local mod_ids = {} |
|
for i = 1, #idx_list do |
|
local item = TreeNodeByPath(obj, idx_list[i]) |
|
table.insert(objs, item) |
|
|
|
table.insert(mod_ids, item.mod and item.mod.id or false) |
|
end |
|
|
|
return GedPasteObjCode(GedSerialize(objs), "Ged: Error duplicating object"), mod_ids |
|
end |
|
|
|
|
|
|
|
|
|
local function ged_verify_set_prop(obj, prop_meta, prop_id, value, socket) |
|
value = GedToGameValue(value, prop_meta, obj) |
|
if value == nil then |
|
value = obj:GetDefaultPropertyValue(prop_id, prop_meta) |
|
elseif prop_meta.validate then |
|
local err, corrected_value = prop_meta.validate(obj, value, socket) |
|
if err then |
|
|
|
GedForceUpdateObject(obj) |
|
ObjModified(obj) |
|
|
|
return GedTranslate(err, obj, false) |
|
elseif corrected_value ~= nil then |
|
value = corrected_value |
|
end |
|
end |
|
|
|
local old_value |
|
if type(prop_id) == "string" then |
|
old_value = obj:GetProperty(prop_id) |
|
else |
|
old_value = obj[prop_id] |
|
end |
|
if ValueToLuaCode(value) ~= ValueToLuaCode(old_value) then |
|
return nil, value, obj:ClonePropertyValue(old_value, prop_meta) |
|
end |
|
end |
|
|
|
local function ged_set_prop(socket, obj, prop_id, value, old_value, multi) |
|
if type(prop_id) == "string" then |
|
obj:SetProperty(prop_id, value) |
|
else |
|
obj[prop_id] = value |
|
end |
|
ParentTableModified(value, obj, "recursive") |
|
GedMultiSelectAdapterObjModified(obj) |
|
socket:NotifyEditorSetProperty(obj, prop_id, old_value, multi) |
|
ObjModified(obj) |
|
end |
|
|
|
local function ged_set_props(socket, prop_id, values, old_values) |
|
local multi = #table.keys(values) > 1 |
|
for obj, value in pairs(values) do |
|
ged_set_prop(socket, obj, prop_id, value, old_values[obj], multi) |
|
end |
|
end |
|
|
|
function GedSetProperty(socket, obj, prop_id, value, disable_undo, slider_drag_id) |
|
local objs = IsKindOf(obj, "GedMultiSelectAdapter") and obj.__objects or { obj } |
|
local values, old_values, prop_captures = {}, {}, {} |
|
for _, item in ipairs(objs) do |
|
local prop_meta = GedIsValidObject(item) and item:GetPropertyMetadata(prop_id) |
|
if prop_meta then |
|
local err, val, old_val = ged_verify_set_prop(item, prop_meta, prop_id, value, socket) |
|
if err then |
|
return err |
|
elseif val ~= nil then |
|
values[item] = val |
|
old_values[item] = old_val |
|
prop_captures[item] = GedPropCapture(item) |
|
end |
|
end |
|
end |
|
|
|
|
|
if next(values) then |
|
ged_set_props(socket, prop_id, values, old_values) |
|
if IsKindOf(obj, "GedMultiSelectAdapter") and not value then |
|
obj:ClearNestedProperty(prop_id) |
|
end |
|
ObjModified(obj) |
|
|
|
if not disable_undo then |
|
|
|
local undo_fns = {} |
|
for _, item in ipairs(objs) do |
|
undo_fns[#undo_fns + 1] = GedCreatePropValuesUndoFn(item, prop_captures[item]) |
|
end |
|
return nil, function() for _, undo in ipairs(undo_fns) do undo() end ObjModified(obj) end, slider_drag_id |
|
else |
|
return nil, nil, slider_drag_id |
|
end |
|
end |
|
end |
|
|
|
function GedPropEditorButton(socket, obj, root_name, prop_id, btn_name, btn_func, btn_param, idx) |
|
if not btn_func then |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local buttons = prop_eval(prop_meta.buttons, obj, prop_meta) |
|
btn_func = table.find_value(buttons, "name", btn_name).func |
|
end |
|
|
|
local game_objects = IsEditorActive() and socket:GatherAffectedGameObjects(obj) |
|
if game_objects then |
|
XEditorUndo:BeginOp{ name = btn_name, objects = game_objects } |
|
end |
|
Msg("GedExecPropButtonStarted", obj) |
|
|
|
local root = socket:ResolveObj(root_name) |
|
local new_sel_or_err, undo_fn |
|
if IsKindOf(obj, "GedMultiSelectAdapter") then |
|
new_sel_or_err, undo_fn = obj:ExecPropButton(root, prop_id, socket, btn_func, btn_param, idx) |
|
else |
|
local prop_capture = not game_objects and GedPropCapture(obj) |
|
if type(btn_func) == "function" then |
|
new_sel_or_err, undo_fn = btn_func(obj, root, prop_id, socket, btn_param, idx) |
|
else |
|
new_sel_or_err, undo_fn = "Couldn't find property button function " .. (btn_func or "nil"), nil |
|
if PropObjHasMember(obj, btn_func) then |
|
new_sel_or_err, undo_fn = obj[btn_func](obj, root, prop_id, socket, btn_param, idx) |
|
elseif obj:IsKindOf("XTemplateWindow") and PropObjHasMember(g_Classes[obj.__class], btn_func) then |
|
new_sel_or_err, undo_fn = g_Classes[obj.__class][btn_func](obj, root, prop_id, socket, btn_param, idx) |
|
elseif PropObjHasMember(root, btn_func) then |
|
new_sel_or_err, undo_fn = root[btn_func](root, obj, prop_id, socket, btn_param, idx) |
|
elseif rawget(_G, btn_func) then |
|
new_sel_or_err, undo_fn = _G[btn_func](root, obj, prop_id, socket, btn_param, idx) |
|
end |
|
end |
|
|
|
|
|
local undo_props = not game_objects and GedCreatePropValuesUndoFn(obj, prop_capture) |
|
if undo_props then |
|
local old_undo_fn = undo_fn or empty_func |
|
undo_fn = function() old_undo_fn() undo_props() end |
|
end |
|
end |
|
|
|
Msg("GedExecPropButtonCompleted", obj) |
|
if game_objects then |
|
XEditorUndo:EndOp(game_objects) |
|
elseif type(new_sel_or_err) ~= "string" and undo_fn then |
|
GedDisplayTempStatus("buttons_undo", "Press Ctrl-Z for undo", 800) |
|
end |
|
return new_sel_or_err, undo_fn |
|
end |
|
|
|
function GedInvokeMethod(socket, obj, fn_name, ...) |
|
obj[fn_name](obj, ...) |
|
end |
|
|
|
function GedDiscardEditorChanges(ged) |
|
for _, group in ipairs(ged:ResolveObj("root")) do |
|
for _, preset in ipairs(group) do |
|
QueueReloadAllPresets(preset:GetLastSavePath(), "Modified", "force_reload") |
|
end |
|
end |
|
end |
|
|
|
function GedNotifyPropertyChanged(socket, parent_name, prop_id) |
|
socket:NotifyEditorSetProperty(socket:ResolveObj(parent_name), prop_id) |
|
end |
|
|
|
function GedGetObjectClass(obj) |
|
return obj and obj.class |
|
end |
|
|
|
|
|
|
|
|
|
function TreeNodeChildren(node) |
|
local f = node and node.GedTreeChildren |
|
return f and (f(node) or empty_table) or node |
|
end |
|
|
|
function RecursiveFindTreeItemPath(parent, obj, path) |
|
path = path or {} |
|
for idx, node in ipairs(TreeNodeChildren(parent)) do |
|
path[#path + 1] = idx |
|
if node == obj or RecursiveFindTreeItemPath(node, obj, path) then |
|
return path |
|
end |
|
table.remove(path) |
|
end |
|
end |
|
|
|
function RecursiveFindTreeItemPaths(root, objects) |
|
local obj_paths = {} |
|
for _, obj in ipairs(objects) do |
|
obj_paths[#obj_paths + 1] = RecursiveFindTreeItemPath(root, obj) |
|
end |
|
|
|
local parent_path = obj_paths[1] |
|
if parent_path then |
|
parent_path = table.copy(parent_path) |
|
local objects_idxs = { table.remove(parent_path) } |
|
for i = 2, #objects do |
|
local obj_path = obj_paths[i] |
|
objects_idxs[#objects_idxs + 1] = table.remove(obj_path) |
|
if not table.iequal(parent_path, obj_path) then |
|
return |
|
end |
|
end |
|
return obj_paths[1], objects_idxs |
|
end |
|
end |
|
|
|
function ParentNodeByPath(root, path) |
|
for i = 1, #path - 1 do |
|
if not root then return end |
|
local f = root.GedTreeChildren |
|
root = rawget(f and (f(root) or empty_table) or root, path[i]) |
|
end |
|
return root |
|
end |
|
|
|
function GetNodeByPath(root, path) |
|
return TreeNodeChildren(ParentNodeByPath(root, path))[path[#path]] |
|
end |
|
|
|
function TreeRelocateNode(root, old_path, new_path) |
|
local old_parent = ParentNodeByPath(root, old_path) |
|
local new_parent = ParentNodeByPath(root, new_path) |
|
local node = table.remove(TreeNodeChildren(old_parent), old_path[#old_path]) |
|
local parent_table = TreeNodeChildren(new_parent) |
|
local new_path_idx = Min(new_path[#new_path], #parent_table + 1) |
|
table.insert(parent_table, new_path_idx, node) |
|
ParentTableModified(node, parent_table) |
|
ObjModified(old_parent) |
|
ObjModified(new_parent) |
|
ObjModified(root) |
|
end |
|
|
|
function GetLowestCommonAncestor(path1, path2) |
|
local lca_path = {} |
|
local index = 1 |
|
local node1 = path1[1] |
|
if not next(path1) or not next(path2) then return lca_path end |
|
while path1[index] == path2[index] do |
|
table.insert(lca_path, path1[index]) |
|
index = index + 1 |
|
end |
|
return lca_path |
|
end |
|
|
|
function IsTreeMultiSelection(selection) |
|
return type(selection) == "table" and (selection[1] == false or type(selection[1]) == "table") |
|
end |
|
|
|
function GedOpTreeMoveItemUp(socket, root, selection) |
|
if IsTreeMultiSelection(selection) then |
|
return GedTreeMultiOp(socket, root, "GedOpTreeMoveItemUp", "forward", selection) |
|
end |
|
|
|
if not selection then return end |
|
local new_path = table.copy(selection) |
|
local leaf_idx = table.remove(new_path) |
|
if leaf_idx > 1 then |
|
table.insert(new_path, leaf_idx - 1) |
|
TreeRelocateNode(root, selection, new_path) |
|
return new_path, function() TreeRelocateNode(root, new_path, selection) end |
|
end |
|
end |
|
|
|
function GedOpTreeMoveItemDown(socket, root, selection) |
|
if IsTreeMultiSelection(selection) then |
|
return GedTreeMultiOp(socket, root, "GedOpTreeMoveItemDown", "backward", selection) |
|
end |
|
|
|
if not selection then return end |
|
local parent = ParentNodeByPath(root, selection) |
|
local new_path = table.copy(selection) |
|
local leaf_idx = table.remove(new_path) |
|
if leaf_idx < #TreeNodeChildren(parent) then |
|
table.insert(new_path, leaf_idx + 1) |
|
TreeRelocateNode(root, selection, new_path) |
|
return new_path, function() TreeRelocateNode(root, new_path, selection) end |
|
end |
|
end |
|
|
|
function GedOpTreeMoveItemInwards(socket, root, selection) |
|
if IsTreeMultiSelection(selection) then |
|
return GedTreeMultiOp(socket, root, "GedOpTreeMoveItemInwards", "forward_inwards", selection) |
|
end |
|
|
|
if not selection then return end |
|
local parent = ParentNodeByPath(root, selection) |
|
local new_path = table.copy(selection) |
|
local leaf_idx = table.remove(new_path) |
|
if leaf_idx > 1 then |
|
local parent_leaf_idx = selection.parent_leaf_idx or (leaf_idx - 1) |
|
local new_parent = TreeNodeChildren(parent)[parent_leaf_idx] |
|
if not IsKindOf(new_parent, "Container") or not new_parent:IsValidSubItem(TreeNodeChildren(ParentNodeByPath(root, selection))[leaf_idx]) then |
|
return "error" |
|
end |
|
table.insert(new_path, parent_leaf_idx) |
|
table.insert(new_path, #TreeNodeChildren(new_parent) + 1) |
|
TreeRelocateNode(root, selection, new_path) |
|
return new_path, function() TreeRelocateNode(root, new_path, selection) end |
|
end |
|
end |
|
|
|
function GedOpTreeMoveItemOutwards(socket, root, selection) |
|
if IsTreeMultiSelection(selection) then |
|
return GedTreeMultiOp(socket, root, "GedOpTreeMoveItemOutwards", "backward_outwards", selection) |
|
end |
|
|
|
if not selection then return end |
|
local new_path = table.copy(selection) |
|
local leaf_idx = table.remove(new_path) |
|
if #new_path > 0 then |
|
table.insert(new_path, table.remove(new_path) + 1) |
|
local new_parent = ParentNodeByPath(root, new_path) |
|
if IsKindOf(new_parent, "Container") and not new_parent:IsValidSubItem(TreeNodeChildren(ParentNodeByPath(root, selection))[leaf_idx]) then |
|
return "error" |
|
end |
|
TreeRelocateNode(root, selection, new_path) |
|
return new_path, function() TreeRelocateNode(root, new_path, selection) end |
|
end |
|
end |
|
|
|
function RelocateTreeNodes(socket, root, selection, target_path) |
|
if not selection then return end |
|
|
|
|
|
|
|
local node_path, sel_idxs |
|
if IsTreeMultiSelection(selection) then |
|
node_path = selection[1] |
|
sel_idxs = selection[2] |
|
table.sort(sel_idxs) |
|
else |
|
node_path = selection |
|
sel_idxs = { selection[#selection] } |
|
end |
|
|
|
local target_parent = ParentNodeByPath(root, target_path) |
|
if not IsKindOf(target_parent, "Container") then |
|
return "error" |
|
end |
|
local old_parent = ParentNodeByPath(root, node_path) |
|
local old_children = TreeNodeChildren(old_parent) |
|
for _, sel_idx in ipairs(sel_idxs) do |
|
if not target_parent:IsValidSubItem(old_children[sel_idx]) then |
|
return "error" |
|
end |
|
end |
|
local nodes = sync_set() |
|
for _, sel_idx in ipairs(sel_idxs) do |
|
nodes:insert(old_children[sel_idx]) |
|
old_children[sel_idx] = false |
|
end |
|
local target_children = TreeNodeChildren(target_parent) |
|
local target_idx = target_path[#target_path] |
|
for _, node in ripairs(nodes) do |
|
table.insert(target_children, target_idx, node) |
|
ParentTableModified(node, target_children) |
|
end |
|
|
|
local hole_idxs = {} |
|
for i = #old_children, 1, -1 do |
|
if not old_children[i] then |
|
table.remove(old_children, i) |
|
hole_idxs[#hole_idxs + 1] = i |
|
end |
|
end |
|
|
|
local new_sel_idxs = {} |
|
for i, child in ipairs(target_children) do |
|
if nodes[child] then |
|
new_sel_idxs[#new_sel_idxs + 1] = i |
|
end |
|
end |
|
|
|
ObjModified(old_parent) |
|
ObjModified(target_parent) |
|
ObjModified(root) |
|
local sel_path = RecursiveFindTreeItemPath(root, target_children[new_sel_idxs[1]]) |
|
local new_selection = { sel_path, new_sel_idxs } |
|
return new_selection, function() |
|
|
|
|
|
|
|
|
|
|
|
for _, hole_idx in ripairs(hole_idxs) do |
|
table.insert(old_children, hole_idx, false) |
|
end |
|
for i in ripairs(nodes) do |
|
table.remove(target_children, target_idx + i - 1) |
|
end |
|
for i, sel_idx in ipairs(sel_idxs) do |
|
local node = nodes[i] |
|
old_children[sel_idx] = node |
|
ParentTableModified(node, target_children) |
|
end |
|
ObjModified(old_parent) |
|
ObjModified(target_parent) |
|
ObjModified(root) |
|
end |
|
end |
|
|
|
function GedOpTreeDropItemUp(socket, root, selection, drop_path) |
|
|
|
return RelocateTreeNodes(socket, root, selection, drop_path) |
|
end |
|
|
|
function GedOpTreeDropItemDown(socket, root, selection, drop_path) |
|
drop_path[#drop_path] = drop_path[#drop_path] + 1 |
|
return RelocateTreeNodes(socket, root, selection, drop_path) |
|
end |
|
|
|
function GedOpTreeDropItemInwards(socket, root, selection, drop_path) |
|
drop_path[#drop_path + 1] = 1 |
|
return RelocateTreeNodes(socket, root, selection, drop_path) |
|
end |
|
|
|
function GedOpTreeDeleteItem(socket, root, selection) |
|
if IsTreeMultiSelection(selection) then |
|
return GedTreeMultiOp(socket, root, "GedOpTreeDeleteItem", "delete", selection) |
|
end |
|
|
|
if not selection then return end |
|
|
|
local parent = ParentNodeByPath(root, selection) |
|
local orig_path = table.copy(selection) |
|
local leaf_idx = table.remove(selection) |
|
local children = TreeNodeChildren(parent) |
|
local node = table.remove(children, leaf_idx) |
|
if not node then |
|
return "invalid selection" |
|
end |
|
|
|
local new_path = table.copy(selection) |
|
if leaf_idx <= #children then |
|
table.insert(new_path, leaf_idx) |
|
elseif leaf_idx > 1 then |
|
table.insert(new_path, leaf_idx - 1) |
|
elseif #new_path == 0 then |
|
new_path = false |
|
end |
|
ObjModified(root) |
|
if root ~= parent then |
|
ObjModified(parent) |
|
end |
|
|
|
local mod_id = node.mod and node.mod.id |
|
local undo_data = GedSerialize(node) |
|
GedNotifyRecursive(node, "OnEditorDelete", parent, socket) |
|
node:delete() |
|
GedNotifyRecursive(node, "OnAfterEditorDelete", parent, socket) |
|
return new_path, function() |
|
local err, node = LuaCodeToTuple(undo_data, _G) |
|
if err then |
|
print("Ged: Error restoring object", err) |
|
return |
|
end |
|
GedOpTreeNewItem(socket, root, table.copy(orig_path), node, leaf_idx, mod_id) |
|
end |
|
end |
|
|
|
function GedOpTreeNewItemInContainer(socket, root, path, class) |
|
path = path or {} |
|
|
|
|
|
local path_len |
|
local obj = root |
|
local item_class = g_Classes[class] |
|
for i, key in ipairs(path) do |
|
if not obj then break end |
|
if IsKindOf(obj, "Container") and obj:IsValidSubItem(item_class) then |
|
path_len = i |
|
end |
|
obj = rawget(TreeNodeChildren(obj), key) |
|
end |
|
|
|
if (not path_len or path_len ~= #path) and IsKindOf(obj, "Container") and obj:IsValidSubItem(item_class) then |
|
path[#path + 1] = #obj |
|
path_len = #path |
|
end |
|
|
|
if path_len then |
|
|
|
table.iclear(path, path_len + 1) |
|
return GedOpTreeNewItem(socket, root, path, class) |
|
end |
|
end |
|
|
|
|
|
function GedOpTreeNewItem(socket, root, path, class_or_instance, idx, mod_id) |
|
path = path or {} |
|
|
|
local parent = ParentNodeByPath(root, path) |
|
local selected = TreeNodeByPath(root, unpack_params(path)) |
|
if not GedIsValidObject(parent) and not GedIsValidObject(selected) or idx == "child" then |
|
idx, parent = "child", selected |
|
end |
|
while parent ~= root and IsKindOf(parent, "Container") and not parent:IsValidSubItem(class_or_instance) do |
|
table.remove(path) |
|
parent = ParentNodeByPath(root, path) |
|
end |
|
if IsKindOf(parent, "Container") and not parent:IsValidSubItem(class_or_instance) then |
|
return "error" |
|
end |
|
|
|
local item = class_or_instance |
|
if type(item) == "string" then |
|
item = _G[item]:new() |
|
GedNotifyRecursive(item, "OnEditorNew", parent, socket, nil, nil, mod_id) |
|
end |
|
|
|
local children = TreeNodeChildren(parent) |
|
if not IsParentTableOf(root, item) then |
|
local leaf_idx = max_int |
|
if idx ~= "child" then |
|
leaf_idx = idx or (#path == 0 and 1 or path[#path] + 1) |
|
end |
|
if leaf_idx > #children then |
|
table.insert(children, item) |
|
else |
|
table.insert(children, leaf_idx, item) |
|
end |
|
ParentTableModified(item, children) |
|
end |
|
PopulateParentTableCache(item) |
|
GedNotifyRecursive(item, "OnAfterEditorNew", parent, socket, nil, nil, mod_id) |
|
|
|
ObjModified(root) |
|
if parent ~= root then |
|
ObjModified(parent) |
|
end |
|
|
|
|
|
if ParentTableCache[item] == children then |
|
if idx ~= "child" then table.remove(path) end |
|
table.insert(path, table.find(children, item)) |
|
else |
|
path = RecursiveFindTreeItemPath(root, item) |
|
end |
|
return path, function() GedOpTreeDeleteItem(socket, root, table.copy(path)) end |
|
end |
|
|
|
function GedOpTreeCut(socket, root, selection, item_class) |
|
GedOpTreeCopy(socket, root, selection, item_class) |
|
return GedOpTreeDeleteItem(socket, root, selection) |
|
end |
|
|
|
function GedOpTreeCopy(socket, root, selection, item_class) |
|
local node_path, idxs = selection[1], selection[2] |
|
if node_path then |
|
local parent = ParentNodeByPath(root, node_path) |
|
GedCopyToClipboard(parent, item_class, idxs) |
|
end |
|
end |
|
|
|
function GedOpTreePaste(socket, root, selection, items, mod_ids) |
|
if type(items) ~= "table" and GedClipboard.base_class == "PropertiesContainer" then |
|
return GedOpPropertyPaste(socket) |
|
end |
|
|
|
local path = selection[1] |
|
local parent = path and ParentNodeByPath(root, path) or root |
|
local paste_into = false |
|
local restore_from_clipboard = not items or type(items) == "string" |
|
if restore_from_clipboard then |
|
local wrong_container_sibling = GedClipboard.base_class and IsKindOf(parent, "Container") and not parent:IsValidSubItem(GedClipboard.base_class) |
|
if parent ~= root and (not GedIsValidObject(parent) or wrong_container_sibling) then |
|
local children = TreeNodeChildren(parent) |
|
local selected_idxs = selection[2] or empty_table |
|
table.sort(selected_idxs) |
|
local idx = (#selected_idxs ~= 0 and selected_idxs[#selected_idxs] or #children) |
|
local node = children[idx] |
|
if not GedIsValidObject(node) or GedClipboard.base_class and IsKindOf(node, "Container") and node:IsValidSubItem(GedClipboard.base_class) then |
|
parent = node |
|
table.insert(selection[1], 1) |
|
selection[2] = { 1 } |
|
path = selection[1] |
|
paste_into = true |
|
end |
|
end |
|
|
|
if GedClipboard.base_class and IsKindOf(parent, "Container") and not parent:IsValidSubItem(GedClipboard.base_class) then |
|
return string.format("Can't paste %s items as children of this object, container of %s.", GedClipboard.base_class, tostring(parent.ContainerClass)) |
|
end |
|
items = GedRestoreFromClipboard(items) |
|
end |
|
|
|
if items then |
|
local selected_idxs = selection[2] or empty_table |
|
table.sort(selected_idxs) |
|
|
|
local children = TreeNodeChildren(parent) |
|
local leaf_idx = (#selected_idxs ~= 0 and selected_idxs[#selected_idxs] or #children) + 1 |
|
if paste_into then leaf_idx = leaf_idx - 1 end |
|
for i, item in ipairs(items) do |
|
local mod_id = restore_from_clipboard and GedClipboard.mod_ids and GedClipboard.mod_ids[i] or (mod_ids and mod_ids[i]) |
|
GedNotifyRecursive(item, "OnEditorNew", parent, socket, "paste", nil, mod_id) |
|
if not IsParentTableOf(root, item) then |
|
if leaf_idx > #children then |
|
table.insert(children, item) |
|
else |
|
table.insert(children, leaf_idx, item) |
|
end |
|
ParentTableModified(item, children) |
|
end |
|
PopulateParentTableCache(item) |
|
leaf_idx = leaf_idx + 1 |
|
end |
|
|
|
if items[1] and items[1]:IsKindOf("Preset") then |
|
items[1]:SortPresets() |
|
end |
|
|
|
|
|
for i, item in ipairs(items) do |
|
local mod_id = restore_from_clipboard and GedClipboard.mod_ids and GedClipboard.mod_ids[i] or (mod_ids and mod_ids[i]) |
|
GedNotifyRecursive(item, "OnAfterEditorNew", parent, socket, "paste", nil, mod_id) |
|
end |
|
ObjModified(root) |
|
if parent ~= root then |
|
ObjModified(parent) |
|
end |
|
|
|
|
|
local items_in_children = true |
|
for _, item in ipairs(items) do |
|
if ParentTableCache[item] ~= children then |
|
items_in_children = false |
|
break |
|
end |
|
end |
|
local sel = {} |
|
if items_in_children then |
|
for _, item in ipairs(items) do |
|
table.insert(sel, table.find(children, item)) |
|
end |
|
path = table.copy(path) |
|
path[Max(#path, 1)] = sel[1] |
|
else |
|
path, sel = RecursiveFindTreeItemPaths(root, items) |
|
end |
|
|
|
local selection = path and { path, sel } or nil |
|
return selection, function() GedOpTreeDeleteItem(socket, root, selection) end |
|
end |
|
end |
|
|
|
function GedOpTreeDuplicate(socket, root, selection, duplicated_class) |
|
local node_path, idxs = selection[1], selection[2] |
|
if not node_path then return end |
|
|
|
local parent = ParentNodeByPath(root, node_path) |
|
for _, idx in ipairs(idxs) do |
|
if duplicated_class and not IsKindOf(TreeNodeChildren(parent)[idx], duplicated_class) then |
|
return "error" |
|
end |
|
end |
|
local items, mod_ids = GedDuplicateObjects(parent, idxs) |
|
if items then |
|
return GedOpTreePaste(socket, root, selection, items, mod_ids) |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
function GedOpListNewItemInClass(socket, obj, index, class_or_instance, parent_class) |
|
if not IsKindOf(obj, parent_class) then return "error" end |
|
return GedOpListNewItem(socket, obj, index, class_or_instance) |
|
end |
|
|
|
function GedOpListNewItem(socket, obj, index, class_or_instance) |
|
if IsKindOf(obj, "Container") and not obj:IsValidSubItem(class_or_instance) then return "error" end |
|
|
|
local item = class_or_instance |
|
if type(item) == "string" then |
|
item = _G[item]:new() |
|
GedNotifyRecursive(item, "OnEditorNew", obj, socket) |
|
end |
|
index = (index or #obj) + 1 |
|
if index > #obj then |
|
table.insert(obj, item) |
|
else |
|
table.insert(obj, index, item) |
|
end |
|
ParentTableModified(item, obj, "recursive") |
|
GedNotifyRecursive(item, "OnAfterEditorNew", obj, socket) |
|
ObjModified(obj) |
|
index = table.find(obj, item) |
|
return index, function() GedOpListDeleteItem(socket, obj, index) end |
|
end |
|
|
|
function GedOpListDeleteItem(socket, obj, selection) |
|
if type(selection) == "table" then |
|
return GedListMultiOp(socket, obj, "GedOpListDeleteItem", "delete", selection) |
|
end |
|
|
|
if not selection or not obj[selection] then return end |
|
local item = table.remove(obj, selection) |
|
local undo_data = GedSerialize(item) |
|
GedNotifyRecursive(item, "OnEditorDelete", obj, socket) |
|
if item then |
|
item:delete() |
|
end |
|
GedNotifyRecursive(item, "OnAfterEditorDelete", obj, socket) |
|
ObjModified(obj) |
|
|
|
return Clamp(selection, 1, #obj), function() |
|
local err, item = LuaCodeToTuple(undo_data, _G) |
|
if err then |
|
print("Ged: Error restoring object", err) |
|
return |
|
end |
|
GedOpListNewItem(socket, obj, selection - 1, item) |
|
end |
|
end |
|
|
|
function GedOpListCut(socket, obj, selection, base_class) |
|
if #selection == 0 then return end |
|
GedOpListCopy(socket, obj, selection, base_class) |
|
return GedOpListDeleteItem(socket, obj, selection) |
|
end |
|
|
|
function GedOpListCopy(socket, obj, selection, base_class) |
|
if #selection == 0 then return end |
|
GedCopyToClipboard(obj, base_class, selection) |
|
end |
|
|
|
function GedOpListPaste(socket, obj, selection, items) |
|
if type(items) ~= "table" and GedClipboard.base_class == "PropertiesContainer" then |
|
local target = obj[selection[#selection]] |
|
return GedOpPropertyPaste(socket, target) |
|
end |
|
|
|
local index = (#selection > 0 and selection[#selection] or #obj) + 1 |
|
if not items or type(items) == "string" then |
|
if GedClipboard.base_class and IsKindOf(obj, "Container") and not obj:IsValidSubItem(GedClipboard.base_class) then |
|
return "error" |
|
end |
|
items = GedRestoreFromClipboard(items) |
|
end |
|
|
|
if items then |
|
for i = 1, #items do |
|
local item = items[i] |
|
GedNotifyRecursive(item, "OnEditorNew", obj, socket, "paste") |
|
if index > #obj then |
|
table.insert(obj, item) |
|
else |
|
table.insert(obj, index, item) |
|
end |
|
ParentTableModified(item, obj, "recursive") |
|
GedNotifyRecursive(item, "OnAfterEditorNew", obj, socket, "paste") |
|
index = index + 1 |
|
end |
|
ObjModified(obj) |
|
|
|
local sel = {} |
|
for _, item in ipairs(items) do |
|
table.insert(sel, table.raw_find(obj, item)) |
|
end |
|
return sel, function() GedOpListDeleteItem(socket, obj, sel) end |
|
end |
|
end |
|
|
|
function GedOpListDuplicate(socket, obj, selection) |
|
if #selection == 0 then return end |
|
local items = GedDuplicateObjects(obj, selection) |
|
if items then |
|
return GedOpListPaste(socket, obj, { selection[#selection] }, items) |
|
end |
|
end |
|
|
|
function GedOpListMoveUp(socket, obj, selection) |
|
if type(selection) == "table" then |
|
return GedListMultiOp(socket, obj, "GedOpListMoveUp", "forward", selection) |
|
end |
|
|
|
if not selection or selection <= 1 or selection > #obj then return end |
|
obj[selection], obj[selection - 1] = obj[selection - 1], obj[selection] |
|
GedNotify(obj[selection], "OnAfterEditorSwap", obj, socket, selection - 1, selection) |
|
ObjModified(obj) |
|
return selection - 1, function() GedOpListMoveDown(socket, obj, selection - 1) end |
|
end |
|
|
|
function GedOpListMoveDown(socket, obj, selection) |
|
if type(selection) == "table" then |
|
return GedListMultiOp(socket, obj, "GedOpListMoveDown", "backward", selection) |
|
end |
|
|
|
if not selection or selection <= 0 or selection >= #obj then return end |
|
obj[selection], obj[selection + 1] = obj[selection + 1], obj[selection] |
|
GedNotify(obj[selection], "OnAfterEditorSwap", obj, socket, selection, selection + 1) |
|
ObjModified(obj) |
|
return selection + 1, function() GedOpListMoveUp(socket, obj, selection + 1) end |
|
end |
|
|
|
local function ListRelocateItems(socket, list, selection, drop_idx) |
|
if #selection == 0 or not drop_idx then return end |
|
local sel_map = {} |
|
for _, sel_idx in ipairs(selection) do |
|
sel_map[sel_idx] = true |
|
end |
|
local new_list = {} |
|
local i = 1 |
|
local new_list_cnt = 0 |
|
for _, list_item in ipairs(list) do |
|
if new_list_cnt == drop_idx - 1 then |
|
break |
|
end |
|
if not sel_map[i] then |
|
new_list_cnt = new_list_cnt + 1 |
|
new_list[new_list_cnt] = list_item |
|
end |
|
i = i + 1 |
|
end |
|
local new_selection = {} |
|
for _, sel_idx in ipairs(selection) do |
|
new_list_cnt = new_list_cnt + 1 |
|
new_list[new_list_cnt] = list[sel_idx] |
|
new_selection[#new_selection + 1] = new_list_cnt |
|
end |
|
for j = i, #list do |
|
if not sel_map[j] then |
|
new_list_cnt = new_list_cnt + 1 |
|
new_list[new_list_cnt] = list[j] |
|
end |
|
end |
|
local old_list = {} |
|
for j = 1, new_list_cnt do |
|
old_list[j] = list[j] |
|
list[j] = new_list[j] |
|
end |
|
GedNotify(list[selection], "OnAfterEditorDragAndDrop", list, socket) |
|
ObjModified(list) |
|
return new_selection, function() |
|
for j = 1, new_list_cnt do |
|
list[j] = old_list[j] |
|
end |
|
GedNotify(list[selection], "OnAfterEditorDragAndDrop", list, socket) |
|
ObjModified(list) |
|
return selection |
|
end |
|
end |
|
|
|
function GedOpListDropUp(socket, obj, selection, drop_idx) |
|
if type(selection) == "number" then |
|
selection = { selection } |
|
end |
|
if selection[1] < drop_idx then |
|
drop_idx = Max(1, drop_idx - 1) |
|
end |
|
return ListRelocateItems(socket, obj, selection, drop_idx) |
|
end |
|
|
|
function GedOpListDropDown(socket, obj, selection, drop_idx) |
|
if type(selection) == "number" then |
|
selection = { selection } |
|
end |
|
if selection[1] > drop_idx then |
|
drop_idx = Min(#obj, drop_idx + 1) |
|
end |
|
return ListRelocateItems(socket, obj, selection, drop_idx) |
|
end |
|
|
|
|
|
|
|
function GedCreateNestedObj(socket, obj, prop_id, class) |
|
if not class or class == "" then return end |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local old_value = obj:GetProperty(prop_id) |
|
local new_value = _G[class]:new() |
|
GedNotifyRecursive(new_value, "OnEditorNew", obj, socket) |
|
|
|
ged_set_prop(socket, obj, prop_id, new_value, old_value) |
|
GedNotifyRecursive(new_value, "OnAfterEditorNew", obj, socket) |
|
return nil, function() ged_set_prop(socket, obj, prop_id, old_value, new_value) end |
|
end |
|
|
|
if FirstLoad then |
|
g_EditedScript = false |
|
g_EditedScriptParent = false |
|
g_EditedScriptPropMeta = false |
|
end |
|
|
|
function OnMsg.GedObjectModified(obj) |
|
if obj == g_EditedScript then |
|
g_EditedScript:Compile() |
|
ObjModified(g_EditedScriptParent) |
|
end |
|
end |
|
|
|
function OnMsg.GedClosing(ged_id) |
|
if GedConnections[ged_id].app_template == "GedScriptEditor" then |
|
local parent = g_EditedScriptParent |
|
g_EditedScript = false |
|
g_EditedScriptParent = false |
|
g_EditedScriptPropMeta = false |
|
ObjModified(parent) |
|
end |
|
end |
|
|
|
function GedSaveScriptParentPreset(socket) |
|
if g_EditedScript then |
|
local preset = GetParentTableOfKind(g_EditedScript, "Preset") |
|
CreateRealTimeThread(function() |
|
GedSetUiStatus("preset_save", "Saving...") |
|
preset:Save(true) |
|
Sleep(200) |
|
GedSetUiStatus("preset_save") |
|
end, preset) |
|
end |
|
end |
|
|
|
function GedTestScript(socket) |
|
g_EditedScriptParent:Test(nil, nil, socket) |
|
end |
|
|
|
function GedCreateOrEditScript(socket, obj, prop_id, value) |
|
value = obj:GetProperty(prop_id) or value |
|
|
|
local undo_fn |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
if not value or type(value) == "string" then |
|
local class = value and g_Classes[value] or ScriptProgram |
|
value = class:new{ Params = prop_eval(prop_meta.params, obj, prop_meta) } |
|
ged_set_prop(socket, obj, prop_id, value) |
|
undo_fn = function() ged_set_prop(socket, obj, prop_id, nil) end |
|
end |
|
|
|
g_EditedScript, g_EditedScriptParent, g_EditedScriptPropMeta = value, obj, prop_meta |
|
ObjModified(obj) |
|
if value.err then |
|
value:Compile() |
|
end |
|
|
|
local ged = OpenGedAppSingleton("GedScriptEditor", value, { |
|
WarningsUpdateRoot = "root", |
|
ItemClass = value.ContainerClass, |
|
ScriptPresetClass = GetParentTableOfKind(g_EditedScript, "Preset").class, |
|
ScriptDomain = prop_meta.script_domain |
|
}) |
|
local params = prop_eval(prop_meta.params, obj, prop_meta) |
|
if value.Params ~= params then |
|
ged:ShowMessage("Warning", string.format("Parameters changed from '%s' to '%s'.\n\nPlease fix the script accordingly!", value.Params, params)) |
|
value.Params = params |
|
end |
|
return nil, undo_fn |
|
end |
|
|
|
function GedOpNestedListNewItem(socket, parent, parent_name, property_name, index, class_or_instance) |
|
local list_created = false |
|
local obj = parent:GetProperty(property_name) |
|
local obj_name = string.format("%s.%s", parent_name, property_name) |
|
if not obj then |
|
obj = {} |
|
parent:SetProperty(property_name, obj) |
|
|
|
ObjModified(parent, "instant") |
|
ParentTableModified(obj, parent) |
|
socket:rfnBindObj(obj_name, { parent_name, property_name }) |
|
list_created = true |
|
end |
|
|
|
local selection, undo = GedOpListNewItem(socket, obj, index, class_or_instance) |
|
socket:NotifyEditorSetProperty(parent, property_name) |
|
ObjModified(parent) |
|
return nil, function() |
|
undo() |
|
if list_created then |
|
parent:SetProperty(property_name, false) |
|
end |
|
ObjModified(parent) |
|
end |
|
end |
|
|
|
function GedNestedObjCopy(socket, parent, prop_id, base_class) |
|
GedCopyToClipboard(parent, base_class, { prop_id }) |
|
end |
|
|
|
function GedNestedObjPaste(socket, parent, prop_id, base_class) |
|
if not IsKindOf(g_Classes[GedClipboard.base_class], base_class) then |
|
local obj_text = GedClipboard.base_class == "PropertiesContainer" and "properties" or string.format("a %s object", GedClipboard.base_class) |
|
return string.format("Can't paste %s here.", obj_text) |
|
end |
|
|
|
local old_value = parent:GetProperty(prop_id) |
|
local new_value = GedRestoreFromClipboard(base_class)[1] |
|
GedNotifyRecursive(new_value, "OnEditorNew", parent, socket, "paste") |
|
parent:SetProperty(prop_id, new_value) |
|
ParentTableModified(new_value, parent, "recursive") |
|
GedNotifyRecursive(new_value, "OnAfterEditorNew", parent, socket, "paste") |
|
ObjModified(parent) |
|
return nil, function() parent:SetProperty(prop_id, old_value) ObjModified(parent) end |
|
end |
|
|
|
function GedNestedListCopy(socket, obj, base_class) |
|
if not obj then return end |
|
local idxs = {} |
|
for i = 1, #obj do idxs[#idxs + 1] = i end |
|
GedCopyToClipboard(obj, base_class, idxs) |
|
end |
|
|
|
function GedNestedListPaste(socket, parent, prop_id, base_class) |
|
if not IsKindOf(g_Classes[GedClipboard.base_class], base_class) then |
|
local obj_text = GedClipboard.base_class == "PropertiesContainer" and "properties" or string.format("%s objects", GedClipboard.base_class) |
|
return string.format("Can't paste %s here.", obj_text) |
|
end |
|
|
|
local old_value = parent:GetProperty(prop_id) |
|
local new_value = GedRestoreFromClipboard(base_class) |
|
for _, item in ipairs(new_value) do |
|
GedNotifyRecursive(item, "OnEditorNew", parent, socket, "paste") |
|
end |
|
parent:SetProperty(prop_id, new_value) |
|
ParentTableModified(new_value, parent, "recursive") |
|
for _, item in ipairs(new_value) do |
|
GedNotifyRecursive(item, "OnAfterEditorNew", parent, socket, "paste") |
|
end |
|
ObjModified(parent) |
|
return nil, function() parent:SetProperty(prop_id, old_value) ObjModified(parent) end |
|
end |
|
|
|
|
|
|
|
function GedPresetTree(obj, filter, format) |
|
format = format and format ~= "" and T{format} or nil |
|
local tree = {} |
|
local start_time = GetPreciseTicks() |
|
if filter then |
|
filter:PrepareForFiltering() |
|
end |
|
local total_displayed_items = 0 |
|
for i, group in ipairs(obj or empty_table) do |
|
local tree_group = {} |
|
local preset_ids = { id = tostring(group) } |
|
local displayed_in_group = 0 |
|
for j, preset in ipairs(group) do |
|
local item = { |
|
id = tostring(preset), |
|
name = format and GedTranslate(format, preset, not "check") or preset.id, |
|
rollover = preset:GetPresetRolloverText(), |
|
} |
|
if filter and not filter:FilterObject(preset) then |
|
item.filtered = true |
|
else |
|
displayed_in_group = displayed_in_group + 1 |
|
end |
|
tree_group[j] = item |
|
end |
|
tree_group.name = string.format("%s <color 75 105 198>(%d)", group[1].group, displayed_in_group) |
|
tree_group.collapsed = GedTreePanelCollapsedNodes[group] |
|
tree_group.id = tostring(group) |
|
tree_group.filtered = filter and displayed_in_group == 0 |
|
total_displayed_items = total_displayed_items + displayed_in_group |
|
tree[i] = tree_group |
|
end |
|
local _time = GetPreciseTicks() - start_time |
|
if _time > 500 then |
|
print("GedPresetTree took", _time, "ms") |
|
elseif _time > 50 then |
|
preset_print("GedPresetTree took %i ms", _time) |
|
end |
|
if filter then |
|
filter:DoneFiltering(total_displayed_items) |
|
end |
|
return tree |
|
end |
|
|
|
function GedFormatPresets(obj, filter, format) |
|
local count = 0 |
|
for _, group in ipairs(obj or empty_table) do |
|
count = count + #group |
|
end |
|
return string.format("%s (%d)", GedFormatObject(obj, filter, format), count) |
|
end |
|
|
|
GedOpNewPreset = GedOpTreeNewItem |
|
GedOpPresetDelete = GedOpTreeDeleteItem |
|
GedOpPresetCut = GedOpTreeCut |
|
GedOpPresetCopy = GedOpTreeCopy |
|
GedOpPresetPaste = GedOpTreePaste |
|
GedOpPresetDuplicate = GedOpTreeDuplicate |
|
|
|
if FirstLoad then |
|
GedPresetSaveInProgress = false |
|
GedPresetSaveModsInProgress = false |
|
end |
|
|
|
function GedPresetSave(ged, obj, class_name, force_save_all) |
|
local class = _G[class_name] |
|
if class and not IsValidThread(GedPresetSaveInProgress) then |
|
local thread = CanYield() and CurrentThread() |
|
GedPresetSaveInProgress = CreateRealTimeThread(function() |
|
SuspendObjModified("preset_save") |
|
GedSetUiStatus("preset_save", "Saving...") |
|
class:SaveAll(force_save_all, "by_user_request", ged) |
|
GedSetUiStatus("preset_save") |
|
ResumeObjModified("preset_save") |
|
GedPresetSaveInProgress = false |
|
Wakeup(thread) |
|
end) |
|
if thread then WaitWakeup(30000) end |
|
end |
|
end |
|
|
|
function GedPresetSaveOne(ged, obj, class_name) |
|
obj = ged:ResolveObj(obj) |
|
if IsKindOf(obj, "Preset") and not IsValidThread(GedPresetSaveInProgress) then |
|
local thread = CanYield() and CurrentThread() |
|
GedPresetSaveInProgress = CreateRealTimeThread(function() |
|
SuspendObjModified("preset_save") |
|
GedSetUiStatus("preset_save", "Saving...") |
|
obj:Save("by_user_request", ged) |
|
GedSetUiStatus("preset_save") |
|
ResumeObjModified("preset_save") |
|
GedPresetSaveInProgress = false |
|
Wakeup(thread) |
|
end) |
|
if thread then WaitWakeup(30000) end |
|
end |
|
end |
|
|
|
function GedPresetSaveMods(ged, obj, class_name) |
|
if not IsValidThread(GedPresetSaveModsInProgress) then |
|
local thread = CanYield() and CurrentThread() |
|
GedPresetSaveModsInProgress = CreateRealTimeThread(function() |
|
|
|
local mods = {} |
|
ForEachPresetExtended(class_name, function(preset, group) |
|
if preset.mod and (preset:IsDirty() or preset.mod:IsDirty()) and IsKindOf(preset, "ModItemPreset") and not preset:IsPacked() then |
|
mods[preset.mod] = preset.mod |
|
end |
|
end) |
|
|
|
local can_save = true |
|
for _, mod in pairs(mods) do |
|
if not mod:CanSaveMod(ged) then |
|
can_save = false |
|
break |
|
end |
|
end |
|
if can_save then |
|
GedSetUiStatus("mod_save", "Saving mods...") |
|
for _, mod in pairs(mods) do |
|
mod:SaveWholeMod() |
|
|
|
ObjModifiedMod(mod) |
|
end |
|
GedSetUiStatus("mod_save") |
|
end |
|
|
|
GedSetUiStatus("mod_save") |
|
GedPresetSaveModsInProgress = false |
|
Wakeup(thread) |
|
end) |
|
if thread then WaitWakeup(30000) end |
|
end |
|
end |
|
|
|
function GedOpOpenPresetEditor(socket, presets, selection, class) |
|
if not selection or #selection ~= 2 or #selection[1] ~= 2 then return end |
|
local group = presets[selection[1][1]] |
|
CreateRealTimeThread(function() |
|
for _, sel in ipairs(selection[2]) do |
|
local preset_id = group[sel].id |
|
if group[sel].class == "PresetDef" and PresetDefs[preset_id].DefEditorName then |
|
OpenPresetEditor(preset_id) |
|
end |
|
end |
|
end) |
|
end |
|
|
|
function GedOpSVNShowLog(socket, obj) |
|
if obj and IsKindOf(obj, "Preset") then |
|
local file_path = obj:GetSavePath() |
|
if not file_path then return end |
|
SVNShowLog(file_path) |
|
end |
|
end |
|
|
|
function GedOpSVNShowBlame(socket, obj) |
|
if obj and IsKindOf(obj, "Preset") then |
|
local file_path = obj:GetSavePath() |
|
if not file_path then return end |
|
SVNShowBlame(file_path) |
|
end |
|
end |
|
|
|
local function LocatePreset(obj, class) |
|
if obj == class then |
|
return true |
|
end |
|
if type(obj) ~= "table" or IsT(obj) then |
|
return |
|
end |
|
if obj.class == class then |
|
return true |
|
end |
|
for _, item in ipairs(obj) do |
|
if LocatePreset(item, class) then |
|
return true |
|
end |
|
end |
|
if not obj.class then |
|
return |
|
end |
|
for _, prop in ipairs(obj:GetProperties()) do |
|
if prop.editor == "nested_obj" or prop.editor == "nested_list" or prop.editor == "script" or |
|
prop.editor == "preset_id" or prop.editor == "preset_id_list" or prop.editor == "property_array" or |
|
prop.editor == "combo" or prop.editor == "choice" or prop.editor == "dropdownlist" or prop.editor == "text_picker" |
|
then |
|
local item = obj:GetProperty(prop.id) |
|
if LocatePreset(item, class) then |
|
return true |
|
end |
|
end |
|
end |
|
end |
|
|
|
function GedOpLocatePreset(socket, obj) |
|
if not obj or not IsKindOf(obj, "Preset") then |
|
print("Can locate only presets!") |
|
return |
|
end |
|
local id = obj.id |
|
local hits = {} |
|
PauseInfiniteLoopDetection("LocatePreset") |
|
for name, groups in pairs(Presets) do |
|
for _, presets in ipairs(groups) do |
|
for _, preset in ipairs(presets) do |
|
if preset ~= obj and LocatePreset(preset, id) then |
|
hits[#hits + 1] = {name, preset.group, preset.id, preset.save_in} |
|
end |
|
end |
|
end |
|
end |
|
ResumeInfiniteLoopDetection("LocatePreset") |
|
print(#hits, "locations found for", obj.id) |
|
for i, hit in ipairs(hits) do |
|
print(" ", table.unpack(hit)) |
|
end |
|
end |
|
|
|
if FirstLoad then |
|
l_GoToNext_id = false |
|
l_GoToNext_last = false |
|
end |
|
|
|
function GedOpGoToNext(socket, obj) |
|
if not obj or not IsKindOf(obj, "Preset") then |
|
print("Can locate only presets!") |
|
return |
|
end |
|
local id = obj.id |
|
while true do |
|
if l_GoToNext_id ~= id then |
|
l_GoToNext_id = id |
|
l_GoToNext_last = false |
|
end |
|
local search = not l_GoToNext_last |
|
for name, groups in pairs(Presets) do |
|
for _, presets in ipairs(groups) do |
|
for _, preset in ipairs(presets) do |
|
if search then |
|
if preset ~= obj and LocatePreset(preset, id) then |
|
l_GoToNext_last = preset |
|
print("Next occurence of", id, "is located in", preset.class, preset.group, preset.id, preset.save_in) |
|
CreateRealTimeThread(function() |
|
local ged = OpenPresetEditor(preset.class, preset:EditorContext()) |
|
if ged then |
|
ged:SetSelection("root", PresetGetPath(preset)) |
|
end |
|
end) |
|
return |
|
end |
|
elseif l_GoToNext_last == preset then |
|
search = true |
|
end |
|
end |
|
end |
|
end |
|
if not l_GoToNext_last then |
|
break |
|
end |
|
l_GoToNext_last = false |
|
end |
|
print("Cannot find any reference of", obj.id) |
|
end |
|
|
|
function GedOpSVNShowDiff(socket, obj) |
|
if obj and IsKindOf(obj, "Preset") then |
|
local file_path = obj:GetSavePath() |
|
if not file_path then return end |
|
SVNShowDiff(file_path) |
|
end |
|
end |
|
|
|
|
|
|
|
|
|
function GedOpObjectCut(socket, obj, selection) |
|
if #selection == 0 then return end |
|
GedOpObjectCopy(socket, obj, selection) |
|
return GedOpListDeleteItem(socket, obj, selection) |
|
end |
|
|
|
function GedOpObjectCopy(socket, obj, selection) |
|
if #selection == 0 then return end |
|
local objs = {} |
|
for _, idx in ipairs(selection) do |
|
table.insert(objs, obj[idx]) |
|
end |
|
editor.ClearSel() |
|
editor.AddToSel(objs) |
|
editor.CopyToClipboard() |
|
GedClipboard.base_class = false |
|
|
|
GedDisplayTempStatus("clipboard", string.format("%d game objects copied", #objs)) |
|
end |
|
|
|
function GedOpObjectPaste(socket, obj) |
|
if GedClipboard.base_class == "PropertiesContainer" then |
|
return GedOpPropertyPaste(socket) |
|
end |
|
editor.PasteFromClipboard() |
|
return nil, function() XEditorUndoQueue:UndoRedo("undo") end |
|
end |
|
|
|
function GedOpObjectDuplicate(socket, obj, selection) |
|
GedOpObjectCopy(socket, obj, selection) |
|
return GedOpObjectPaste(socket, obj) |
|
end |
|
|
|
|
|
|
|
|
|
function GedCallMethod(ged, obj_name, method, ...) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
obj[method](obj, ...) |
|
end |
|
|
|
function GedObjModified(ged, obj_name, ...) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
ObjModified(obj) |
|
end |
|
|
|
|
|
|
|
local eval = prop_eval |
|
|
|
function GedOpPropertyCopy(socket, obj, properties, panel_context) |
|
if #properties == 0 then return end |
|
|
|
GedClipboard.base_class = "PropertiesContainer" |
|
local copy_data = { id = {}, value = {}, editor = {}, panel_context = false, } |
|
local os_clipboard_data = {} |
|
for i, id in ipairs(properties) do |
|
local property_value = obj:GetProperty(id) |
|
local meta_data = obj:GetPropertyMetadata(id) |
|
copy_data.id[#copy_data.id + 1] = id |
|
copy_data.value[#copy_data.value + 1] = property_value |
|
local editor = eval(meta_data.editor, obj, meta_data) |
|
copy_data.editor[#copy_data.editor + 1] = editor |
|
local editor_class = g_Classes[GedPropEditors[editor]] |
|
if editor_class and editor_class.ConvertToText then |
|
local text = editor_class:ConvertToText(property_value, meta_data) |
|
text = IsT(text) and TDevModeGetEnglishText(text) or text |
|
if type(text) == "string" and #text > 0 then |
|
os_clipboard_data[#os_clipboard_data + 1] = IsT(text) and TDevModeGetEnglishText(text) or text |
|
end |
|
end |
|
end |
|
copy_data.panel_context = panel_context |
|
GedPropertiesContainer.data = copy_data |
|
GedClipboard.stored_objs = ValueToLuaCode(GedPropertiesContainer) |
|
if #os_clipboard_data > 0 then |
|
CopyToClipboard(table.concat(os_clipboard_data, "\n")) |
|
end |
|
GedDisplayTempStatus("clipboard", string.format("%d properties copied", #properties)) |
|
end |
|
|
|
function ShowPropertyPasteWarning(socket, prop_ids) |
|
local text = "Properties with IDs: " |
|
for i=1, #prop_ids do |
|
text = text..prop_ids[i] |
|
if i == #prop_ids then |
|
text = text.." could not be pasted!" |
|
else |
|
text = text..", " |
|
end |
|
end |
|
socket:ShowMessage("Warning!", text) |
|
end |
|
|
|
local function CanUseTargetProperties(target, source_ids, source_editors, target_ids) |
|
if #source_ids ~= #(target_ids or "") then |
|
return false |
|
end |
|
for i = 1, #source_ids do |
|
local dst_id = target_ids[i] |
|
local dst_meta_data = target:GetPropertyMetadata(dst_id) |
|
if not dst_meta_data then |
|
return false |
|
end |
|
if source_editors[i] ~= eval(dst_meta_data.editor, target, dst_meta_data) then |
|
return false |
|
end |
|
local src_id = source_ids[i] |
|
local src_prefix = string.find(src_id, "%d+$" ) |
|
local dst_prefix = string.find(dst_id, "%d+$" ) |
|
if not src_prefix or src_prefix ~= dst_prefix then |
|
return false |
|
end |
|
if string.sub(src_id, 1, src_prefix - 1) ~= string.sub(dst_id, 1, dst_prefix - 1) then |
|
return false |
|
end |
|
end |
|
return true |
|
end |
|
|
|
function GedOpPropertyOSClipboardPaste(socket, target, target_properties, panel_context) |
|
if not IsKindOf(target, "PropertyObject") or #target_properties == 0 then return end |
|
local objs = IsKindOf(target, "GedMultiSelectAdapter") and target.__objects or { target } |
|
if not objs then return end |
|
local clipboard_split = string.split(GetFromClipboard(-1), "\n") |
|
local old_values |
|
for i = 1, Min(#clipboard_split, #target_properties) do |
|
local target_property = target_properties[i] |
|
local clipboard = clipboard_split[i] |
|
local meta_data = target:GetPropertyMetadata(target_property) |
|
if meta_data and not eval(meta_data.no_edit, target, meta_data) then |
|
local editor_class = g_Classes[GedPropEditors[meta_data.editor]] |
|
if editor_class and editor_class.accept_os_clipboard_paste and editor_class.ConvertFromText then |
|
local ok, value, invalid = pcall(editor_class.ConvertFromText, editor_class, clipboard, meta_data) |
|
if ok or invalid then |
|
local id = meta_data.id |
|
for _, obj in ipairs(objs) do |
|
local old_value = obj:GetProperty(id) |
|
old_values = old_values or {} |
|
old_values[obj] = old_values[obj] or {} |
|
old_values[obj][id] = old_value |
|
ged_set_prop(socket, obj, id, value, old_value) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
if not old_values then return end |
|
ObjModified(target) |
|
socket:OnParentsModified(panel_context) |
|
return nil, function() |
|
for obj, obj_data in pairs(old_values) do |
|
for id, value in pairs(obj_data) do |
|
ged_set_prop(socket, obj, id, value, obj:GetProperty(id)) |
|
end |
|
end |
|
ObjModified(target) |
|
socket:OnParentsModified(panel_context) |
|
end |
|
end |
|
|
|
function GedOpPropertyPaste(socket, target_override, target_properties, panel_context) |
|
local _, os_clipboard_undo_func = GedOpPropertyOSClipboardPaste(socket, target_override, target_properties, panel_context) |
|
if os_clipboard_undo_func then |
|
return nil, os_clipboard_undo_func |
|
end |
|
|
|
if GedClipboard.base_class ~= "PropertiesContainer" or not GedClipboard.stored_objs then return end |
|
|
|
local err, container = LuaCodeToTuple(GedClipboard.stored_objs, _G) |
|
if err then |
|
print("Ged: Error restoring object", err) |
|
return |
|
end |
|
GenerateLocalizationIDs(container) |
|
|
|
local data = container.data |
|
local target = socket:ResolveObj(data.panel_context) |
|
if target_override and target_override:IsKindOf("PropertyObject") then |
|
if CanUseTargetProperties(target_override, data.id, data.editor, data.id) then |
|
target = target_override |
|
end |
|
end |
|
if not target then |
|
print("Failed to find a target to receive the copied properties!") |
|
return |
|
end |
|
local objs = target:IsKindOf("GedMultiSelectAdapter") and target.__objects or { target } |
|
|
|
local dst_prop_ids = CanUseTargetProperties(target, data.id, data.editor, target_properties) and target_properties or data.id |
|
local old_values = {} |
|
local pasted_ids, unmatched_ids = {}, {} |
|
for i = 1, #dst_prop_ids do |
|
local id = dst_prop_ids[i] |
|
local meta_data = target:GetPropertyMetadata(id) |
|
if meta_data and eval(meta_data.editor, target, meta_data) == data.editor[i] and not eval(meta_data.no_edit, target, meta_data) then |
|
local value = data.value[i] |
|
for _, obj in ipairs(objs) do |
|
local old_value = obj:GetProperty(id) |
|
old_values[obj] = old_values[obj] or {} |
|
old_values[obj][id] = old_value |
|
ged_set_prop(socket, obj, id, value, old_value) |
|
end |
|
pasted_ids[#pasted_ids + 1] = id |
|
else |
|
unmatched_ids[#unmatched_ids + 1] = id |
|
end |
|
end |
|
ObjModified(target) |
|
socket:OnParentsModified(data.panel_context) |
|
|
|
socket:Send("rfnApp", "SetPropSelection", data.panel_context, pasted_ids) |
|
if #unmatched_ids ~= 0 then |
|
ShowPropertyPasteWarning(socket, unmatched_ids) |
|
end |
|
|
|
local undo_func = function() |
|
for obj, obj_data in pairs(old_values) do |
|
for id, value in pairs(obj_data) do |
|
ged_set_prop(socket, obj, id, value, obj:GetProperty(id)) |
|
end |
|
end |
|
ObjModified(target) |
|
socket:OnParentsModified(data.panel_context) |
|
end |
|
return nil, undo_func |
|
end |
|
|
|
function GedEditFunction(ged, obj_name, props) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
if not props or #props ~= 1 then return end |
|
|
|
local prop = props[1] |
|
if not prop then return end |
|
|
|
local value = obj:GetProperty(prop) |
|
if IsKindOf(value, "ScriptProgram") then |
|
value = value.eval |
|
end |
|
if type(value) ~= "function" then return end |
|
|
|
local info = debug.getinfo(value, "S") |
|
local first, last = info.linedefined, info.lastlinedefined |
|
local source = info.source |
|
if not info or not source or not first or not last then return end |
|
OpenFileLineInHaerald(source:sub(2), first) |
|
end |
|
|
|
function GedOpPresetIdNewInstance(ged, obj, prop_id, class_name) |
|
local id = ged:WaitUserInput("New Preset Name", class_name) |
|
if not id then return end |
|
|
|
local class = g_Classes[class_name] |
|
local preset = class:new{ id = id } |
|
local presets = Presets[class.PresetClass or class.class] |
|
GedNotifyRecursive(preset, "OnEditorNew", presets[preset.group] or presets, ged) |
|
PopulateParentTableCache(preset) |
|
GedNotifyRecursive(preset, "OnAfterEditorNew", presets[preset.group], ged) |
|
|
|
preset:OpenEditor() |
|
|
|
local old_value = obj:GetProperty(prop_id) |
|
obj:SetProperty(prop_id, preset.id) |
|
ObjModified(obj) |
|
|
|
return false, function() |
|
preset:delete() |
|
obj:SetProperty(prop_id, old_value) |
|
ObjModified(presets) |
|
ObjModified(obj) |
|
end |
|
end |
|
|
|
function PresetIdPropFindInstance(obj, prop_meta, id) |
|
local class_name = eval(prop_meta.preset_class, obj, prop_meta) |
|
local group_name = eval(prop_meta.preset_group, obj, prop_meta) |
|
local class = class_name and g_Classes[class_name] |
|
if not class then |
|
assert(false, "Preset class not found: " .. tostring(class_name)) |
|
return |
|
end |
|
assert(class.GlobalMap or IsPresetWithConstantGroup(class) or group_name) |
|
if class.GlobalMap then |
|
return _G[class.GlobalMap][id] |
|
else |
|
group_name = group_name or class.group |
|
local preset_class = class.PresetClass or class_name |
|
local groups = Presets[preset_class] |
|
local presets = groups and groups[group_name] |
|
return presets and presets[id] |
|
end |
|
end |
|
|
|
function GedRpcEditPreset(ged, obj_name, prop_id, preset_id) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
|
|
if not prop_id and not preset_id then |
|
obj:OpenEditor() |
|
return |
|
end |
|
|
|
preset_id = preset_id or obj:GetProperty(prop_id) |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local preset = PresetIdPropFindInstance(obj, prop_meta, preset_id) |
|
if preset then |
|
preset:OpenEditor() |
|
else |
|
local class = g_Classes[prop_meta.preset_class] |
|
if class then |
|
OpenPresetEditor(class.PresetClass or class.class) |
|
end |
|
end |
|
end |
|
|
|
function GedRpcBindPreset(ged, name, obj_name, prop_id, preset_id) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
|
|
preset_id = preset_id or obj:GetProperty(prop_id) |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local preset = prop_meta and PresetIdPropFindInstance(obj, prop_meta, preset_id) |
|
if preset then |
|
ged:BindObj(name, preset) |
|
else |
|
ged:UnbindObj(name) |
|
end |
|
end |
|
|
|
function GedRpcInspectObj(ged, obj_name, prop_id, prop_obj) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
prop_obj = prop_obj or obj:GetProperty(prop_id) |
|
OpenGedGameObjectEditorInGame(prop_obj) |
|
end |
|
|
|
function GedViewPosButton(root, obj, prop_id, ged) |
|
local pos = obj:GetProperty(prop_id) |
|
if not IsValidPos(pos) then |
|
return |
|
end |
|
local local_space = pos:Len() < 5000 or table.get(obj:GetPropertyMetadata(prop_id), "local_space") |
|
if local_space and (not pos:z() or not IsValidPos(obj)) then |
|
return |
|
end |
|
pos = ValidateZ(pos) |
|
local pos0 = local_space and obj:GetVisualPos() or pos |
|
local vec = local_space and SetLen(pos, 2*guim) or 2*guim |
|
local color = local_space and red or white |
|
local v = PlaceVector(pos0, vec, color) |
|
local c = PlaceCircle(pos0, guim/2, color) |
|
if local_space then |
|
c:SetOrientation(vec) |
|
end |
|
ViewPos(pos0) |
|
Msg("RpcViewPos") |
|
CreateRealTimeThread(function() |
|
WaitMsg("RpcViewPos", 5000) |
|
DoneObject(v) |
|
DoneObject(c) |
|
end) |
|
end |
|
|
|
|
|
|
|
function FindLinkedPresetOfClass(obj, preset_class, id, save_in) |
|
id = id or obj.id |
|
save_in = save_in or obj.save_in |
|
|
|
local found |
|
ForEachPresetExtended(preset_class, function(preset) |
|
if preset.id == id and preset.save_in == save_in then |
|
found = preset |
|
return "break" |
|
end |
|
end) |
|
return found |
|
end |
|
|
|
function GedCreateLinkedPresets(root, obj, prop_id, ged) |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local classes = eval(prop_meta.preset_classes, obj, prop_meta) |
|
for _, preset_class in ipairs(classes) do |
|
local preset = g_Classes[preset_class]:new{ id = obj.id, save_in = obj.save_in } |
|
preset:Register() |
|
for prop, value in pairs(prop_meta.default_props[preset_class]) do |
|
preset:SetProperty(prop, value) |
|
end |
|
for prop, how in pairs(prop_meta.mirror_props[preset_class]) do |
|
if type(how) == "function" then |
|
preset:SetProperty(prop, how(obj)) |
|
else |
|
preset:SetProperty(prop, obj:GetProperty(how)) |
|
end |
|
end |
|
preset:MarkDirty() |
|
ObjModified(Presets[preset_class]) |
|
end |
|
ObjModified(obj) |
|
end |
|
|
|
function GedDeleteLinkedPresets(root, obj, prop_id, ged) |
|
local prop_meta = obj:GetPropertyMetadata(prop_id) |
|
local classes = eval(prop_meta.preset_classes, obj, prop_meta) |
|
for _, preset_class in ipairs(classes) do |
|
local preset = FindLinkedPresetOfClass(obj, preset_class) |
|
if preset then |
|
preset:delete() |
|
ObjModified(Presets[preset_class]) |
|
end |
|
end |
|
ObjModified(obj) |
|
end |
|
|
|
function GedRpcBindLinkedPreset(ged, name, obj_name, preset_class) |
|
local obj = ged:ResolveObj(obj_name) |
|
if not obj then return end |
|
|
|
local preset = FindLinkedPresetOfClass(obj, preset_class) |
|
if preset then |
|
ged:BindObj(name, preset) |
|
else |
|
ged:UnbindObj(name) |
|
end |
|
end |
|
|
|
LinkedPresetClasses = {} |
|
LinkedPresetMirrorProps = {} |
|
|
|
local function add_mirror_prop(class, linked_class, prop_id, how, final) |
|
local prop_data = LinkedPresetMirrorProps[prop_id] or {} |
|
table.insert(prop_data, { copy_from_class = class, copy_to_class = linked_class, how = how }) |
|
LinkedPresetMirrorProps[prop_id] = prop_data |
|
|
|
|
|
if not final and type(how) == "string" then |
|
add_mirror_prop(linked_class, class, how, prop_id, "final") |
|
end |
|
end |
|
|
|
function OnMsg.ClassesPostBuilt() |
|
ClassDescendantsList("Preset", function(class_name, class) |
|
if class.GedEditor and (not class:IsKindOf("CompositeDef") or class.ObjectBaseClass) then |
|
for _, prop_meta in ipairs(class:GetProperties()) do |
|
if prop_meta.editor == "linked_presets" then |
|
local classes = eval(prop_meta.preset_classes, class, prop_meta) |
|
LinkedPresetClasses[class_name] = table.iappend(LinkedPresetClasses[class_name] or {}, prop_meta.classes) |
|
|
|
for _, linked_class in ipairs(classes) do |
|
for prop_id, how in pairs(prop_meta.mirror_props[linked_class]) do |
|
add_mirror_prop(class_name, linked_class, prop_id, how) |
|
end |
|
add_mirror_prop(class_name, linked_class, "SaveIn", "SaveIn") |
|
add_mirror_prop(class_name, linked_class, "Id", "Id") |
|
end |
|
end |
|
end |
|
end |
|
end) |
|
end |
|
|
|
local function mirror_prop(mirror_data, class, obj, prop_id, preset_id, preset_savein, processed_classes) |
|
for _, data in ipairs(mirror_data) do |
|
local from, to = data.copy_from_class, data.copy_to_class |
|
if from == class and not processed_classes[to] then |
|
|
|
local how = data.how |
|
local target = FindLinkedPresetOfClass(obj, to, preset_id, preset_savein) |
|
if target then |
|
if type(how) == "function" then |
|
target:SetProperty(prop_id, how(obj)) |
|
else |
|
print("Set", prop_id, "from", from, "to", to) |
|
target:SetProperty(prop_id, obj:GetProperty(how)) |
|
end |
|
end |
|
|
|
processed_classes[to] = true |
|
mirror_prop(mirror_data, to, obj, prop_id, preset_id, preset_savein, processed_classes) |
|
end |
|
end |
|
end |
|
|
|
local function mirror_prop_message(ged, classes, prop_id) |
|
ged:ShowMessage("Linked Presets", |
|
string.format("<center>The '%s' property was also updated\nin linked preset(s) %s.", prop_id, table.concat(table.keys2(classes, "sorted"), ", "))) |
|
end |
|
|
|
function OnMsg.GedPropertyEdited(ged_id, obj, prop_id, old_value) |
|
local data = LinkedPresetMirrorProps[prop_id] |
|
if data then |
|
local preset_id = prop_id == "Id" and old_value or obj.id |
|
local preset_savein = prop_id == "SaveIn" and old_value or obj.save_in |
|
local processed_classes = { [obj.class] = true } |
|
mirror_prop(data, obj.class, obj, prop_id, preset_id, preset_savein, processed_classes) |
|
|
|
processed_classes[obj.class] = nil |
|
if next(processed_classes) then |
|
DelayedCall(50, mirror_prop_message, GedConnections[ged_id], processed_classes, prop_id) |
|
end |
|
end |
|
end |
|
|
|
function OnMsg.PresetSave(class) |
|
|
|
local dirty_paths = {} |
|
for obj in pairs(g_DirtyObjects) do |
|
for parent_preset, linked_presets in pairs(LinkedPresetClasses) do |
|
if table.find(linked_presets, obj.class) then |
|
local parent_preset = FindLinkedPresetOfClass(obj, parent_preset) |
|
if parent_preset and parent_preset.class == class then |
|
local paths = dirty_paths[obj.class] or {} |
|
paths[obj:GetNormalizedSavePath()] = true |
|
paths[obj:GetLastSavePath()] = true |
|
dirty_paths[obj.class] = paths |
|
end |
|
end |
|
end |
|
end |
|
|
|
for class, paths in pairs(dirty_paths) do |
|
_G[class]:SaveFiles(paths) |
|
end |
|
end |
|
|