Move chat command handling code from C++ to Lua (#5528)
[oweals/minetest.git] / builtin / common / filterlist.lua
1 --Minetest
2 --Copyright (C) 2013 sapier
3 --
4 --This program is free software; you can redistribute it and/or modify
5 --it under the terms of the GNU Lesser General Public License as published by
6 --the Free Software Foundation; either version 2.1 of the License, or
7 --(at your option) any later version.
8 --
9 --This program is distributed in the hope that it will be useful,
10 --but WITHOUT ANY WARRANTY; without even the implied warranty of
11 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 --GNU Lesser General Public License for more details.
13 --
14 --You should have received a copy of the GNU Lesser General Public License along
15 --with this program; if not, write to the Free Software Foundation, Inc.,
16 --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 --------------------------------------------------------------------------------
19 -- TODO improve doc                                                           --
20 -- TODO code cleanup                                                          --
21 -- Generic implementation of a filter/sortable list                           --
22 -- Usage:                                                                     --
23 -- Filterlist needs to be initialized on creation. To achieve this you need to --
24 -- pass following functions:                                                  --
25 -- raw_fct() (mandatory):                                                     --
26 --     function returning a table containing the elements to be filtered      --
27 -- compare_fct(element1,element2) (mandatory):                                --
28 --     function returning true/false if element1 is same element as element2  --
29 -- uid_match_fct(element1,uid) (optional)                                     --
30 --     function telling if uid is attached to element1                        --
31 -- filter_fct(element,filtercriteria) (optional)                              --
32 --     function returning true/false if filtercriteria met to element         --
33 -- fetch_param (optional)                                                     --
34 --     parameter passed to raw_fct to aquire correct raw data                 --
35 --                                                                            --
36 --------------------------------------------------------------------------------
37 filterlist = {}
38
39 --------------------------------------------------------------------------------
40 function filterlist.refresh(self)
41         self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
42         filterlist.process(self)
43 end
44
45 --------------------------------------------------------------------------------
46 function filterlist.create(raw_fct,compare_fct,uid_match_fct,filter_fct,fetch_param)
47
48         assert((raw_fct ~= nil) and (type(raw_fct) == "function"))
49         assert((compare_fct ~= nil) and (type(compare_fct) == "function"))
50         
51         local self = {}
52         
53         self.m_raw_list_fct  = raw_fct
54         self.m_compare_fct   = compare_fct
55         self.m_filter_fct    = filter_fct
56         self.m_uid_match_fct = uid_match_fct
57         
58         self.m_filtercriteria = nil
59         self.m_fetch_param = fetch_param
60         
61         self.m_sortmode = "none"
62         self.m_sort_list = {}
63
64         self.m_processed_list = nil
65         self.m_raw_list = self.m_raw_list_fct(self.m_fetch_param)
66
67         self.add_sort_mechanism = filterlist.add_sort_mechanism
68         self.set_filtercriteria = filterlist.set_filtercriteria
69         self.get_filtercriteria = filterlist.get_filtercriteria
70         self.set_sortmode       = filterlist.set_sortmode
71         self.get_list           = filterlist.get_list
72         self.get_raw_list       = filterlist.get_raw_list
73         self.get_raw_element    = filterlist.get_raw_element
74         self.get_raw_index      = filterlist.get_raw_index
75         self.get_current_index  = filterlist.get_current_index
76         self.size               = filterlist.size
77         self.uid_exists_raw     = filterlist.uid_exists_raw
78         self.raw_index_by_uid   = filterlist.raw_index_by_uid
79         self.refresh            = filterlist.refresh
80
81         filterlist.process(self)
82         
83         return self
84 end
85
86 --------------------------------------------------------------------------------
87 function filterlist.add_sort_mechanism(self,name,fct)
88         self.m_sort_list[name] = fct
89 end
90
91 --------------------------------------------------------------------------------
92 function filterlist.set_filtercriteria(self,criteria)
93         if criteria == self.m_filtercriteria and
94                 type(criteria) ~= "table" then
95                 return
96         end
97         self.m_filtercriteria = criteria
98         filterlist.process(self)
99 end
100
101 --------------------------------------------------------------------------------
102 function filterlist.get_filtercriteria(self)
103         return self.m_filtercriteria
104 end
105
106 --------------------------------------------------------------------------------
107 --supported sort mode "alphabetic|none"
108 function filterlist.set_sortmode(self,mode)
109         if (mode == self.m_sortmode) then
110                 return
111         end
112         self.m_sortmode = mode
113         filterlist.process(self)
114 end
115
116 --------------------------------------------------------------------------------
117 function filterlist.get_list(self)
118         return self.m_processed_list
119 end
120
121 --------------------------------------------------------------------------------
122 function filterlist.get_raw_list(self)
123         return self.m_raw_list
124 end
125
126 --------------------------------------------------------------------------------
127 function filterlist.get_raw_element(self,idx)
128         if type(idx) ~= "number" then
129                 idx = tonumber(idx)
130         end
131         
132         if idx ~= nil and idx > 0 and idx <= #self.m_raw_list then
133                 return self.m_raw_list[idx]
134         end
135         
136         return nil
137 end
138
139 --------------------------------------------------------------------------------
140 function filterlist.get_raw_index(self,listindex)
141         assert(self.m_processed_list ~= nil)
142         
143         if listindex ~= nil and listindex > 0 and
144                 listindex <= #self.m_processed_list then
145                 local entry = self.m_processed_list[listindex]
146                 
147                 for i,v in ipairs(self.m_raw_list) do
148                 
149                         if self.m_compare_fct(v,entry) then
150                                 return i
151                         end
152                 end
153         end
154         
155         return 0
156 end
157
158 --------------------------------------------------------------------------------
159 function filterlist.get_current_index(self,listindex)
160         assert(self.m_processed_list ~= nil)
161         
162         if listindex ~= nil and listindex > 0 and
163                 listindex <= #self.m_raw_list then
164                 local entry = self.m_raw_list[listindex]
165                 
166                 for i,v in ipairs(self.m_processed_list) do
167                 
168                         if self.m_compare_fct(v,entry) then
169                                 return i
170                         end
171                 end
172         end
173         
174         return 0
175 end
176
177 --------------------------------------------------------------------------------
178 function filterlist.process(self)
179         assert(self.m_raw_list ~= nil)
180
181         if self.m_sortmode == "none" and
182                 self.m_filtercriteria == nil then
183                 self.m_processed_list = self.m_raw_list
184                 return
185         end
186         
187         self.m_processed_list = {}
188         
189         for k,v in pairs(self.m_raw_list) do
190                 if self.m_filtercriteria == nil or
191                         self.m_filter_fct(v,self.m_filtercriteria) then
192                         self.m_processed_list[#self.m_processed_list + 1] = v
193                 end
194         end
195         
196         if self.m_sortmode == "none" then
197                 return
198         end
199         
200         if self.m_sort_list[self.m_sortmode] ~= nil and
201                 type(self.m_sort_list[self.m_sortmode]) == "function" then
202                 
203                 self.m_sort_list[self.m_sortmode](self)
204         end
205 end
206
207 --------------------------------------------------------------------------------
208 function filterlist.size(self)
209         if self.m_processed_list == nil then
210                 return 0
211         end
212         
213         return #self.m_processed_list
214 end
215
216 --------------------------------------------------------------------------------
217 function filterlist.uid_exists_raw(self,uid)
218         for i,v in ipairs(self.m_raw_list) do
219                 if self.m_uid_match_fct(v,uid) then
220                         return true
221                 end
222         end
223         return false
224 end
225
226 --------------------------------------------------------------------------------
227 function filterlist.raw_index_by_uid(self, uid)
228         local elementcount = 0
229         local elementidx = 0
230         for i,v in ipairs(self.m_raw_list) do
231                 if self.m_uid_match_fct(v,uid) then
232                         elementcount = elementcount +1
233                         elementidx = i
234                 end
235         end
236         
237         
238         -- If there are more elements than one with same name uid can't decide which
239         -- one is meant. self shouldn't be possible but just for sure.
240         if elementcount > 1 then
241                 elementidx=0
242         end
243
244         return elementidx
245 end
246
247 --------------------------------------------------------------------------------
248 -- COMMON helper functions                                                    --
249 --------------------------------------------------------------------------------
250
251 --------------------------------------------------------------------------------
252 function compare_worlds(world1,world2)
253
254         if world1.path ~= world2.path then
255                 return false
256         end
257         
258         if world1.name ~= world2.name then
259                 return false
260         end
261         
262         if world1.gameid ~= world2.gameid then
263                 return false
264         end
265
266         return true
267 end
268
269 --------------------------------------------------------------------------------
270 function sort_worlds_alphabetic(self)
271
272         table.sort(self.m_processed_list, function(a, b)
273                 --fixes issue #857 (crash due to sorting nil in worldlist)
274                 if a == nil or b == nil then
275                         if a == nil and b ~= nil then return false end
276                         if b == nil and a ~= nil then return true end
277                         return false
278                 end
279                 if a.name:lower() == b.name:lower() then
280                         return a.name < b.name
281                 end
282                 return a.name:lower() < b.name:lower()
283         end)
284 end
285
286 --------------------------------------------------------------------------------
287 function sort_mod_list(self)
288
289         table.sort(self.m_processed_list, function(a, b)
290                 -- Show game mods at bottom
291                 if a.typ ~= b.typ then
292                         return b.typ == "game_mod"
293                 end
294                 -- If in same or no modpack, sort by name
295                 if a.modpack == b.modpack then
296                         if a.name:lower() == b.name:lower() then
297                                 return a.name < b.name
298                         end
299                         return a.name:lower() < b.name:lower()
300                 -- Else compare name to modpack name
301                 else
302                         -- Always show modpack pseudo-mod on top of modpack mod list
303                         if a.name == b.modpack then
304                                 return true
305                         elseif b.name == a.modpack then
306                                 return false
307                         end
308                         
309                         local name_a = a.modpack or a.name
310                         local name_b = b.modpack or b.name
311                         if name_a:lower() == name_b:lower() then
312                                 return  name_a < name_b
313                         end
314                         return name_a:lower() < name_b:lower()
315                 end
316         end)
317 end