File size: 7,403 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
__Undefined = rawget( _G, "__Undefined") or {}
setmetatable(__Undefined, {
	__toluacode = function(self, indent, pstr)
		local code = "__Undefined"
		if pstr then
			return pstr:append(code)
		else
			return code
		end
	end,
	__tostring = function(self)
		return "__Undefined__"
	end
})
function Undefined()
	return __Undefined
end

if FirstLoad then
	GedMultiSelectAdapters = setmetatable({}, weak_keys_meta)
end

DefineClass.GedMultiSelectAdapter = {
	__parents = { "PropertyObject", "InitDone" },
	__objects = {}, -- try to prevent name collisions; we store nested objs/lists directly in the object
	properties = false,
	property_merge_union = "any", -- or "all"
}

local dont_evaluate = { default = true, preset_filter = true, class_filter = true, setter = true, getter = true, dont_save = true }
local special_treat = { max = Min, min = Max }
local eval = prop_eval
local function MergePropMeta(prop_accumulator, prop_meta, object)
	if prop_meta.no_edit and eval(prop_meta.no_edit, object, prop_meta) then
		return nil
	end
	
	if not prop_accumulator then
		prop_accumulator = {}
		for meta_name, value in pairs(prop_meta) do
			prop_accumulator[meta_name] = dont_evaluate[meta_name] and value or eval(value, object, prop_meta)
		end
		if prop_accumulator.default == nil then
			prop_accumulator.default = PropertyObject.GetDefaultPropertyValue(object, prop_meta.id, prop_meta)
		end
		prop_accumulator.__count = 1
	else
		for meta_name, value in pairs(prop_meta) do
			value = dont_evaluate[meta_name] and value or eval(value, object, prop_meta)
			local acc_value = prop_accumulator[meta_name]
			local special_treat_fn = special_treat[meta_name]
			if special_treat_fn then
				prop_accumulator[meta_name] = special_treat_fn(acc_value, value)
			elseif not CompareValues(acc_value, value) then
				return nil
			end
		end
		prop_accumulator.__count = prop_accumulator.__count + 1
	end
	return prop_accumulator
end

local function CalculatePropertyMetadata(objects)
	local prop_ids = {} -- keep the order of the properties.
	local properties_accumulator = {}
	local prop_ids_with_mismatches = {}
	
	for _, object in ipairs(objects) do
		local properties = object:GetProperties()
		
		for _, prop_meta in ipairs(properties) do
			if not prop_ids_with_mismatches[prop_meta.id] then
				local acc = properties_accumulator[prop_meta.id]
				if not acc then
					prop_ids[#prop_ids+1] = prop_meta.id
				end
				local resulting_acc = MergePropMeta(acc, prop_meta, object)
				properties_accumulator[prop_meta.id] = resulting_acc
				if resulting_acc == nil then
					prop_ids_with_mismatches[prop_meta.id] = true
				end
			end
		end
	end
	
	-- convert to standard prop table
	return properties_accumulator, prop_ids
end

local function MultiselectAdapterGetProperties(objects, property_merge_union)
	local props = {}
	local metas, order = CalculatePropertyMetadata(objects)

	for _, prop_id in ipairs(order) do
		local meta = metas[prop_id]
		if meta then
			if property_merge_union == "any" or (meta.__count == #objects and property_merge_union == "all") then
				table.insert(props, meta)
				props[prop_id] = meta
			end
		end
	end
	return props
end

function GedMultiSelectAdapter:Init()
	local objects = self.__objects
	for i = #objects, 1, -1 do
		if not GedIsValidObject(objects[i]) then
			table.remove(objects, i)
		end
	end
	for _, obj in ipairs(objects) do
		Msg("GedBindObj", obj)
	end
	
	self:UpdatePropertyMetadatas()
	GedMultiSelectAdapters[self] = true
end

function GedMultiSelectAdapter:UpdatePropertyMetadatas()
	self.properties = MultiselectAdapterGetProperties(self.__objects, self.property_merge_union)
	
	-- creates copies of any nested_obj/nested_list properties in the object for Ged to edit
	for _, prop in ipairs(self.properties) do
		local editor, id = prop.editor, prop.id
		if editor == "nested_obj" or editor == "nested_list" then
			local value = self:GetProperty(id)
			if value ~= Undefined() and rawget(self, id) == nil then
				rawset(self, id, self:CopyNestedObjOrList(value))
			end
		end
	end
end

function GedMultiSelectAdapter:CopyNestedObjOrList(value)
	local ret = false
	if value then
		if IsKindOf(value, "PropertyObject") then
			ret = value:Clone()
		else
			ret = {}
			for i, item in ipairs(value) do
				ret[i] = item:Clone()
			end
		end
	end
	return ret
end

function GedMultiSelectAdapter:CopyNestedPropValueToObjects(id)
	for _, obj in ipairs(self.__objects) do
		obj:SetProperty(id, self:CopyNestedObjOrList(self[id]))
	end
end

function GedMultiSelectAdapter:OnEditorSetProperty(prop_id, old_value, ged)
	local prop_meta = self:GetPropertyMetadata(prop_id)
	if prop_meta.editor == "nested_obj" or prop_meta.editor == "nested_list" then
		self:CopyNestedPropValueToObjects(prop_id)
	end
end

function GedMultiSelectAdapter:ClearNestedProperty(prop_id)
	local prop_meta = self:GetPropertyMetadata(prop_id)
	if prop_meta.editor == "nested_obj" or prop_meta.editor == "nested_list" then
		self[prop_id] = nil
	end
end

function GedMultiSelectAdapterObjModified(obj)
	if IsKindOf(obj, "GedMultiSelectAdapter") then
		obj:UpdatePropertyMetadatas()
		return
	end
	for adapter in pairs(GedMultiSelectAdapters) do
		for id, value in pairs(adapter) do
			if (rawequal(obj, value) or type(value) == "table" and table.find(value, obj)) and table.find(adapter.properties, "id", id) then
				adapter:CopyNestedPropValueToObjects(id)
			end
		end
	end
end

OnMsg.GedObjectModified = GedMultiSelectAdapterObjModified

function GedMultiSelectAdapter:GetProperty(prop_id)
	local value = rawget(self, prop_id)
	if value ~= nil then
		return value
	end
	for _, obj in ipairs(self.__objects) do
		if GedIsValidObject(obj) then
			local new_val = GetProperty(obj, prop_id)
			if new_val ~= nil then
				if value ~= nil and not CompareValues(new_val, value) then
					return Undefined()
				end
				value = new_val
			end
		end
	end
	return value
end

function GedMultiSelectAdapter:GetDefaultPropertyValue(prop_id, prop_meta)
	if prop_meta then
		local value = rawget(prop_meta, "default")
		if value ~= nil then
			return value
		end
	end
	return self.properties[prop_id].default
end

function GedMultiSelectAdapter:ExecPropButton(root, prop_id, ged, func, param)
	SuspendObjModified("GedMultiSelectAdapter:ExecPropButton")
	local errs, undos = {}, {}
	for _, obj in ipairs(self.__objects) do
		if GedIsValidObject(obj) then
			local err, undo
			local prop_capture = GedPropCapture(obj)
			if type(func) == "function" then
				err, undo = func(obj, root, prop_id, ged, param)
			elseif obj:HasMember(func) then
				err, undo = obj[func](obj, root, prop_id, ged, param)
			elseif type(rawget(_G, func)) == "function" then
				err, undo = _G[func](root, obj, prop_id, ged, param)
			end
			
			local undop = GedCreatePropValuesUndoFn(obj, prop_capture)
			if type(err)   == "string"   then errs [#errs  + 1] = err   end
			if type(undo)  == "function" then undos[#undos + 1] = undo  end
			if type(undop) == "function" then undos[#undos + 1] = undop end
		end
	end
	ResumeObjModified("GedMultiSelectAdapter:ExecPropButton")
	
	return
		next(errs) and table.concat(errs, "\n"),
		#undos > 0 and function() for i = 1, #undos do undos[i]() end end or nil
end

function GedMultiSelectAdapter:OnEditorSelect(...)
	for _, obj in ipairs(self.__objects) do
		if GedIsValidObject(obj) and PropObjHasMember(obj, "OnEditorSelect") then
			obj:OnEditorSelect(...)
		end
	end
end