File size: 8,769 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
local function XPopupWindowWithSearch(anchor, title, create_children_func)
	local popup = OpenDialog("GedNestedElementsList", terminal.desktop)
	popup.OnShortcut = function(self, shortcut)
		if shortcut == "Escape" then
			popup:Close()
			return "break"
		elseif shortcut == "Down" then
			popup.idLeftList.idWin.idList:SetFocus()
		end
	end
	
	local list = popup.idLeftList.idWin.idList
	popup.idTitle:SetText(title)
	local edit = popup.idLeftList.idSearch
	local container = popup.idRightList
	
	edit.OnTextChanged = function(edit) 
		create_children_func(popup, list, container, edit:GetText())
	end
	
	edit.OnShortcut = function(edit, shortcut, source, ...)
		if shortcut == "Escape" then
			return
		elseif shortcut == "Enter" and edit:GetText() then
			local list = edit.parent.idWin.idList
			if list[1] and list[1]:IsKindOf("XTextButton") then
				list[1]:OnPress()
				return
			end
		end
		return XEdit.OnShortcut(edit, shortcut, source, ...)
	end
	
	list.OnShortcut = function(list, shortcut, source, ...)
		local relation = XShortcutToRelation[shortcut]
		if shortcut == "Down" or shortcut == "Up" or relation == "down" or relation == "up" then
			local focus = list.desktop.keyboard_focus
			local order = focus and focus:GetFocusOrder()
			if shortcut == "Down" or relation == "down" then
				focus = list:GetRelativeFocus(order or point(0, 0), "next")
			else
				focus = list:GetRelativeFocus(order or point(1000000000, 1000000000), "prev")
			end
			if focus then
				list:ScrollIntoView(focus)
				focus:SetFocus()
			end
			return "break"
		end
	end
	
	create_children_func(popup, list, container, "", "create")
	popup:SetModal(true)
	edit:SetFocus()
end

local function XPopupListWithSearch(anchor, create_children_func)
	local popup = XPopup:new({}, terminal.desktop)
	popup.OnKillFocus = function(self)
		if self.window_state == "open" then
			popup:Close()
		end
	end
	popup.OnShortcut = function(self, shortcut)
		if shortcut == "Escape" then
			popup:Close()
			return "break"
		elseif shortcut == "Down" then
			popup.idPopupList:SetFocus()
		end
	end
	local list = XPopupList:new({
		Id = "idPopupList",
		AutoFocus = false,
		Dock = "bottom",
		MaxItems = 10,
		BorderWidth = 0,
		min_width = false,
		OnShortcut = function(list, shortcut, source, ...) -- let the popup handle escapes
			if shortcut == "Escape" then
				return
			end
			return XPopupList.OnShortcut(list, shortcut, source, ...)
		end,
		Measure = function(self, max_width, max_height) -- do not fold the search popup after items have been filtered
			local _, height
			if not self.min_width then
				self.min_width, height = XPopupList.Measure(self, max_width, max_height)
			else
				_, height =  XPopupList.Measure(self, max_width, max_height)
			end
			return self.min_width, height
		end,
		OnKillFocus = function(self, new_focus) end,
	}, popup)
	
	local edit = XEdit:new({
		Dock = "top",
		Id = "idSearch",
		Margins = box(2, 2, 2, 2),
		OnTextChanged = function(edit) 
			create_children_func(popup, list.idContainer, edit:GetText())
		end,
		OnShortcut = function(list, shortcut, source, ...)
			if shortcut == "Escape" then
				return
			end
			return XEdit.OnShortcut(list, shortcut, source, ...)
		end,
	}, popup)
	
	popup:SetAnchor(anchor.box)
	popup:SetAnchorType("drop-right")
	popup:SetScaleModifier(anchor.scale)
	popup:SetOutsideScale(point(1000, 1000))
	popup:Open()
	
	create_children_func(popup, list.idContainer, "")
	if #list.idContainer > 5 then
		edit:SetFocus()
	else
		edit:delete()
		popup:SetFocus()
	end
end

function FillUsageSegments(list, segments)
	local sum = 0
	local sorted = {}
	for _, item in ipairs(list) do
		if item.use_count then
			sum = sum + item.use_count
			table.insert(sorted, item)
		end
	end
	
	table.sortby_field(sorted, "use_count")
	
	local tally, target, segment = 0, 0, 0
	for _, item in ipairs(sorted) do
		tally = tally + item.use_count
		if tally > target then
			segment = segment + 1
			target = MulDivRound(sum, segment, segments)
		end
		item.usage_segment = segment
	end
	return sum
end

function GedOpenCreateItemPopup(panel, title, items, button, create_callback)
	if items and #items == 1 then
		create_callback(items[1].value)
		return
	end
	
	local defined = 0
	for i = 1, #items do
		local cat = items[i].category
		if cat and cat ~= "" and cat ~= "General" then
			defined = defined + 1
		end
	end
	if defined >= 3 then
		XPopupWindowWithSearch(button, title, function(popup, list, container, search_text, create)
			local least_dim = GetDarkModeSetting() and 255 or 32
			local most_dim = GetDarkModeSetting() and 128 or 140
			local modifiable_zone = most_dim - least_dim
			
			local suffixes = {
				"",
				"<right>•",
				"<right>••",
				"<right>•••",
			}
			local uses_total = FillUsageSegments(items, 3)
			
			local create_button = function(idx, item, container, popup, right)
				local entry = XTextButton:new({ UseXTextControl = true }, container)
				entry:SetFocusOrder(point(1, idx))
				entry:SetLayoutMethod("Box")
				entry.idLabel:SetHAlign("stretch")
				if right and search_text == "" and uses_total ~= 0 then
					local gamma = most_dim - MulDivRound(modifiable_zone, item.usage_segment, 3)
					entry:SetText(string.format("<color %d %d %d>%s%s", gamma, gamma, gamma, item.text, suffixes[item.usage_segment + 1]))
				elseif right and item.usage_segment then
					entry:SetText(item.text .. suffixes[item.usage_segment + 1])
				else
					entry:SetText(item.text)
				end
				entry.OnPress = function(entry)
					button:SetFocus()
					create_callback(item.value)
					button:SetFocus(false)
					if popup.window_state ~= "destroying" then
						popup:Close()
					end
				end
				
				if item.documentation or item.use_count then
					local texts = {}
					table.insert(texts, item.documentation or string.format("<style GedTitle><center>%s</style>", item.value))
					if item.use_count_in_preset then
						table.insert(texts, item.use_count and string.format("<style GedSmall>Used %d times in %s presets.", item.use_count, item.use_count_in_preset))
					end
					entry:SetRolloverText(table.concat(texts, "\n\n"))
					entry:SetRolloverTemplate("GedToolbarRollover")
					entry:SetRolloverAnchor("bottom")
				end
				
				return entry
			end
			
			local categories_list = {}
			local currently_searching = false
			
			-- left side
			list:DeleteChildren()
			local to_find = string.lower(search_text)
			for i, item in ipairs(items) do
				if string.lower(item.text):find(to_find, 1, true) then
					create_button(i, item, list, popup)
				end
				local category = item.category ~= "" and item.category or "General"
				local list = categories_list[category] or {}
				list[#list + 1] = item
				categories_list[category] = list
			end
			if #list == 0 then
				XText:new({}, list):SetText("No items to choose from.")
			end
			
			-- right side
			local categories_list_indexed = {}
			for name, list in sorted_pairs(categories_list) do
				categories_list_indexed[#categories_list_indexed + 1] = { category = name, elements = list }
			end		
			
			for idx, info in ipairs(categories_list_indexed) do
				local win
				if not create then
					for _, pane in ipairs(container) do
						local title = pane:ResolveId("idCategoryTitle")
						if title and title:GetText() == info.category then
							win = pane
							break
						end
					end
				end
				if not win then
					win = XTemplateSpawn("GedNestedElementsCategory", container)
					win:ResolveId("idCategoryTitle"):SetText(info.category)
				end
				
				local sublist = win:ResolveId("idCategoryElements")
				if sublist then
					sublist:DeleteChildren()
					local items = info.elements
					for i, item in ipairs(items) do
						local entry = create_button(i, item, sublist, popup, "right")
						entry:SetEnabled(not not string.lower(item.text):find(to_find, 1, true))
					end
				end
			end
			panel.app:UpdateChildrenDarkMode(popup)
		end)
	else
		XPopupListWithSearch(button, function(popup, container, search_text)
			container:DeleteChildren()
			local to_find = string.lower(search_text)
			for i, item in ipairs(items) do
				if string.lower(item.text):find(to_find, 1, true) then
					local entry = XTemplateSpawn("XComboListItem", container, false)
					entry:SetFocusOrder(point(1, i))
					entry:SetFontProps(XCombo)
					entry:SetText(item.text)
					entry:SetMinHeight(entry:GetFontHeight())
					entry.OnPress = function(entry)
						button:SetFocus()
						create_callback(item.value)
						button:SetFocus(false)
						if popup.window_state ~= "destroying" then
							popup:Close()
						end
					end
				end
			end
			if #container == 0 then
				XText:new({}, container):SetText("No items to choose from.")
			end
			panel.app:UpdateChildrenDarkMode(popup)
		end)
	end
end