File size: 10,908 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
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
local unavailable_msg = "Not available in game mode! Retry in the editor!"

function Collection:GetLocked()
	return self.Index == editor.GetLockedCollectionIdx()
end

function Collection:SetLocked(locked)
	local idx = self.Index
	if idx == 0 then
		return
	end
	local prev_locked = self:GetLocked()
	if locked and prev_locked or not locked and not prev_locked then
		return
	end
	Collection.UnlockAll()
	if prev_locked then
		return
	end
	editor.ClearSel()
	editor.SetLockedCollectionIdx(idx)
	MapSetGameFlags(const.gofWhiteColored, "map", "CObject")
	MapForEach("map", "collection", idx, true, function(o) o:ClearHierarchyGameFlags(const.gofWhiteColored) end)
end

function Collection.GetLockedCollection()
	local locked_idx = editor.GetLockedCollectionIdx()
	return locked_idx ~= 0 and Collections[locked_idx]
end

function Collection.UnlockAll()
	if editor.GetLockedCollectionIdx() == 0 then
		return false
	end
	editor.SetLockedCollectionIdx(0)
	MapClearGameFlags(const.gofWhiteColored, "map", "CObject")
	return true
end

-- clone the collections in the given group of objects
function Collection.Duplicate(objects)
	local duplicated = {}
	local collections = {}
	-- clone and assign collections:
	local locked_idx = editor.GetLockedCollectionIdx()
	for i = 1, #objects do
		local obj = objects[i]
		if IsValid(obj) then
			local col = obj:GetCollection()
			if not col then
				obj:SetCollectionIndex(locked_idx)
			elseif col.Index ~= locked_idx then
				local new_col = duplicated[col]
				if not new_col then
					new_col = col:Clone()
					duplicated[col] = new_col
					collections[#collections + 1] = col
				end
				obj:SetCollection(new_col)
			else
				obj:SetCollection(col)
			end
		end
	end
	
	-- fix collection hierarchy
	local i = #collections
	while i > 0 do
		local col = collections[i]
		local new_col = duplicated[col]
		local parent = col:GetCollection()
		i = i - 1
		
		if parent and parent.Index ~= locked_idx then
			local new_parent = duplicated[parent]
			if not duplicated[parent] then
				new_parent = parent:Clone()
				duplicated[parent] = new_parent
				i = i + 1
				collections[i] = parent
			end
			new_col:SetCollection(new_parent)
		else
			new_col:SetCollectionIndex(locked_idx)
		end
	end
	
	UpdateCollectionsEditor()
end

function Collection.UpdateLocked()
	editor.SetLockedCollectionIdx(editor.GetLockedCollectionIdx())
end

function OnMsg.NewMap()
	editor.SetLockedCollectionIdx(0)
end

----

DefineClass.CollectionContent = {
	__parents = { "PropertyObject" },
	properties = {},
	col = false,
	children = false,
	objects = false,

	EditorView = Untranslated("<Name> <style GedConsole><color 0 255 200><Index></color></style>"),
}

function CollectionContent:GedTreeChildren()
	return self.children
end

function CollectionContent:GetName()
	local name = self.col.Name
	return #name > 0 and name or "[Unnamed]"
end

function CollectionContent:GetIndex()
	local index = self.col.Index
	return index > 0 and index or ""
end

function CollectionContent:SelectInEditor()
	local ged = GetCollectionsEditor()
	if not ged then
		return
	end

	local root = ged:ResolveObj("root")

	local path = {}
	local iter = self
	while iter and iter ~= root do
		local parent_idx = iter.col:GetCollectionIndex()
		if parent_idx and parent_idx > 0 then
			local parent = root.collection_to_gedrepresentation[Collections[parent_idx]]
			table.insert(path, 1, table.find(parent.children, iter))
			iter = parent
		else
			table.insert(path, 1, table.find(root, iter))
			break
		end
	end
	ged:SetSelection("root", path)
end


function CollectionContent:OnEditorSelect(selected, ged)
	local is_initial_selection = not ged:ResolveObj("CollectionObjects")
	
	if selected then
		ged:BindObj("CollectionObjects", self.objects) -- for idObjects panel
		ged:BindObj("SelectedObject", self.col) -- for idProperties panel
	end

	if not IsEditorActive() then
		return
	end
	if selected then
		-- If this is the initial selection (when the editor is first opened) => don't move the camera
		ged:ResolveObj("root"):Select(self, not is_initial_selection and "show_in_editor")
	end
end


function CollectionContent:ActionUnlockAll()
	if not IsEditorActive() then
		print(unavailable_msg)
		return
	end
	Collection.UnlockAll()
end

----

DefineClass.CollectionRoot = {
	__parents = { "InitDone" },

	collection_to_gedrepresentation = false,
	selected_col = false,
}

function GedCollectionEditorOp(ged, name)
	if not IsEditorActive() then
		print(unavailable_msg)
		return
	end
	
	local gedcol = ged:ResolveObj("SelectedCollection")
	local root = ged:ResolveObj("root")
	local col = gedcol and gedcol.col
	local col_to_select = false
	
	if not col then
		return
	end
	
	if name == "new" then
		Collection.Collect()
	elseif name == "delete" then
		-- Prepare next collection to be selected in the editor
		local root_index = table.find(root, gedcol) or 0
		local nextColContent = root[root_index + 1]
		if nextColContent and nextColContent:GetIndex() ~= 0 then
			col_to_select = Collections[nextColContent:GetIndex()]
		end
		col:Destroy()
	elseif name == "lock" then
		col:SetLocked(true)
	elseif name == "unlock" then
		Collection.UnlockAll()
	elseif name == "collect" then
		col_to_select = Collection.Collect(editor.GetSel())
	elseif name == "uncollect" then
		DoneObject(col)
	elseif name == "view" then
		if gedcol and gedcol.objects then
			ViewObjects(gedcol.objects)
		end
	end
	root:UpdateTree()
	
	if root.collection_to_gedrepresentation and col_to_select then
		-- Select a new collection in the editor
		local gedrepr = root.collection_to_gedrepresentation[col_to_select]
		if gedrepr then
			gedrepr:SelectInEditor()
		end
	end
end

function CollectionRoot:Select(obj, show_in_editor)
	if not IsEditorActive() or self.selected_collection == obj.col then
		return
	end

	local col = obj.col
	if not col:GetLocked() then
		local parent = col:GetCollection()
		if parent then
			parent:SetLocked(true)
		else
			Collection.UnlockAll()
		end
	end
	
	if show_in_editor then
		local col_objects = MapGet("map", "attached", false, "collection", col.Index)
		editor.ChangeSelWithUndoRedo(col_objects, "dont_notify")
		ViewObjects(col_objects)
	end
	
	self.selected_collection = obj.col
end

function CollectionRoot:Init()
	self:UpdateTree()
end

function CollectionRoot:SelectPlainCollection(col)
	local obj = self.collection_to_gedrepresentation[col]
	if obj then
		self.selected_collection = obj.col
		obj:SelectInEditor()
	end
end

function CollectionRoot:UpdateTree()
	table.iclear(self)
	if not Collections then
		return
	end
	self.collection_to_gedrepresentation = {}
	local collection_to_children = {}
	local col_to_objs = {}
	MapForEach("map", "attached", false, "collected", true, function(obj, col_to_objs)
		local idx = obj:GetCollectionIndex()
		col_to_objs[idx] = table.create_add(col_to_objs[idx], obj)
	end, col_to_objs)
	local count = 0
	for col_idx, col_obj in sorted_pairs(Collections) do
		local objects = col_to_objs[col_idx] or {}
		table.sortby_field(objects, "class")
		collection_to_children[col_obj.Index] = collection_to_children[col_obj.Index] or {}
		local children = collection_to_children[col_obj.Index]
		local gedrepr = CollectionContent:new({col = col_obj, objects = objects, children = children})
		self.collection_to_gedrepresentation[col_obj] = gedrepr

		local parent_index = col_obj:GetCollectionIndex()
		if parent_index > 0 then
			collection_to_children[parent_index] = collection_to_children[parent_index] or {}
			table.insert(collection_to_children[parent_index], gedrepr)
		else
			count = count + 1
			self[count] = gedrepr
		end
	end
	table.sort(self, function(a, b) a, b = a.col.Name, b.col.Name return #a > 0 and #b == 0 or #a > 0 and a < b end)
	ObjModified(self)
end

function OnMsg.EditorCallback(id)
	if id == "EditorCallbackPlace" then
		UpdateCollectionsEditor()
	end
end

local openingCollectionEditor = false
function OpenCollectionEditorAndSelectCollection(obj)
	if openingCollectionEditor then return end
	openingCollectionEditor = true -- deal with multi selection and multiple calls from the button
	CreateRealTimeThread(function()
		local col = obj and obj:GetRootCollection()
		if not col then
			return
		end
		local ged = GetCollectionsEditor()
		if not ged then
			OpenCollectionsEditor(col)
			while not ged do
				Sleep(100)
				ged = GetCollectionsEditor()
			end
		end
		
		openingCollectionEditor = false
	end)
end

function OnMsg.EditorSelectionChanged(objects)
	local ged = GetCollectionsEditor()
	if not ged then
		return
	end
	local col = objects and objects[1] and objects[1]:GetRootCollection()
	if not col then return end

	local root = ged:ResolveObj("root")
	root:SelectPlainCollection(col)
end

local function get_auto_selected_collection()
	-- is the editor selection a single collection?
	local count, collections = editor.GetSelUniqueCollections()
	if count == 1 then
		return next(collections)
	end
	
	return Collection.GetLockedCollection()
end

function OpenCollectionsEditor(collection_to_select)
	local ged = GetCollectionsEditor()
	if not ged then
		collection_to_select = collection_to_select or get_auto_selected_collection()
		
		CreateRealTimeThread(function()
			ged = OpenGedApp("GedCollectionsEditor", CollectionRoot:new{}) or false
			
			while not ged do
				Sleep(100)
				ged = GetCollectionsEditor()
			end
			
			local root = ged:ResolveObj("root")
			
			if collection_to_select then
				-- Wait for the initial GedPanel selection to finish (to call OnEditorSelect()) to avoid an infinite selection loop
				Sleep(100)
				root:SelectPlainCollection(collection_to_select)
				return
			end
			
			local firstColContent = root and root[1]
			local select_col = collection_to_select or (root and root[1])
			
			-- Select the first collection in the editor
			if firstColContent and firstColContent:GetIndex() ~= 0 then
				local firstCollection = Collections[firstColContent:GetIndex()]
				root:SelectPlainCollection(firstCollection)
			end
		end)
	end
	return ged
end

function GetCollectionsEditor()
	for id, ged in pairs(GedConnections) do
		if IsKindOf(ged:ResolveObj("root"), "CollectionRoot") then
			return ged
		end
	end
end

function UpdateCollectionsEditor(ged)
	if ged then
		local root = ged:ResolveObj("root")
		if root then
			root:UpdateTree()
		end
	else
		ged = GetCollectionsEditor()
		if ged then
			DelayedCall(0, UpdateCollectionsEditor, ged)
		end
	end
end

function Collection:SetParentButton(_, __, ged)
	local parent = self.Graft ~= "" and CollectionsByName[ self.Graft ]
	if parent then
		local col = parent.Index
		while true do
			if col == 0 then break end
			if col == self.Index then
				printf("Can't set %s as parent, because it is a child of %s", self.Graft, self.Name)
				return
			end
			col = Collections[col]:GetCollectionIndex()
		end
		self:SetCollectionIndex( parent.Index )
	else
		self:SetCollectionIndex(0)
	end
	UpdateCollectionsEditor(ged)
end