1---------------------------------------------------------------------------
2--- (Deprecated) class module for icon lookup for menubar
3--
4-- @author Kazunobu Kuriyama
5-- @copyright 2015 Kazunobu Kuriyama
6-- @classmod menubar.icon_theme
7---------------------------------------------------------------------------
8
9-- This implementation is based on the specifications:
10--  Icon Theme Specification 0.12
11--  http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-0.12.html
12
13local beautiful = require("beautiful")
14local gfs = require("gears.filesystem")
15local GLib = require("lgi").GLib
16local index_theme = require("menubar.index_theme")
17
18local ipairs = ipairs
19local setmetatable = setmetatable
20local string = string
21local table = table
22local math = math
23
24local get_pragmatic_base_directories = function()
25    local dirs = {}
26
27    local dir = GLib.build_filenamev({GLib.get_home_dir(), ".icons"})
28    if gfs.dir_readable(dir) then
29        table.insert(dirs, dir)
30    end
31
32    dir = GLib.build_filenamev({GLib.get_user_data_dir(), "icons"})
33    if gfs.dir_readable(dir) then
34        table.insert(dirs, dir)
35    end
36
37    for _, v in ipairs(GLib.get_system_data_dirs()) do
38        dir = GLib.build_filenamev({v, "icons"})
39        if gfs.dir_readable(dir) then
40            table.insert(dirs, dir)
41        end
42    end
43
44    local need_usr_share_pixmaps = true
45    for _, v in ipairs(GLib.get_system_data_dirs()) do
46        dir = GLib.build_filenamev({v, "pixmaps"})
47        if gfs.dir_readable(dir) then
48            table.insert(dirs, dir)
49        end
50        if dir == "/usr/share/pixmaps" then
51            need_usr_share_pixmaps = false
52        end
53    end
54
55    dir = "/usr/share/pixmaps"
56    if need_usr_share_pixmaps and gfs.dir_readable(dir) then
57        table.insert(dirs, dir)
58    end
59
60    return dirs
61end
62
63local get_default_icon_theme_name = function()
64    local icon_theme_names = { "Adwaita", "gnome", "hicolor" }
65    for _, dir in ipairs(get_pragmatic_base_directories()) do
66        for _, icon_theme_name in ipairs(icon_theme_names) do
67            local filename = string.format("%s/%s/index.theme", dir, icon_theme_name)
68            if gfs.file_readable(filename) then
69                return icon_theme_name
70            end
71        end
72    end
73    return "hicolor"
74end
75
76local icon_theme = { mt = {} }
77
78local index_theme_cache = {}
79
80--- Class constructor of `icon_theme`
81-- @deprecated menubar.icon_theme.new
82-- @tparam string icon_theme_name Internal name of icon theme
83-- @tparam table base_directories Paths used for lookup
84-- @treturn table An instance of the class `icon_theme`
85icon_theme.new = function(icon_theme_name, base_directories)
86    icon_theme_name = icon_theme_name or beautiful.icon_theme or get_default_icon_theme_name()
87    base_directories = base_directories or get_pragmatic_base_directories()
88
89    local self = {}
90    self.icon_theme_name = icon_theme_name
91    self.base_directories = base_directories
92    self.extensions = { "png", "svg", "xpm" }
93
94    -- Instantiate index_theme (cached).
95    if not index_theme_cache[self.icon_theme_name] then
96        index_theme_cache[self.icon_theme_name] = {}
97    end
98    local cache_key = table.concat(self.base_directories, ':')
99    if not index_theme_cache[self.icon_theme_name][cache_key] then
100        index_theme_cache[self.icon_theme_name][cache_key] = index_theme(
101            self.icon_theme_name,
102            self.base_directories)
103    end
104    self.index_theme = index_theme_cache[self.icon_theme_name][cache_key]
105
106    return setmetatable(self, { __index = icon_theme })
107end
108
109local directory_matches_size = function(self, subdirectory, icon_size)
110    local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
111
112    if kind == "Fixed" then
113        return icon_size == size
114    elseif kind == "Scalable" then
115        return icon_size >= min_size and icon_size <= max_size
116    elseif kind == "Threshold" then
117        return icon_size >= size - threshold and icon_size <= size + threshold
118    end
119
120    return false
121end
122
123local directory_size_distance = function(self, subdirectory, icon_size)
124    local kind, size, min_size, max_size, threshold = self.index_theme:get_per_directory_keys(subdirectory)
125
126    if kind == "Fixed" then
127        return math.abs(icon_size - size)
128    elseif kind == "Scalable" then
129        if icon_size < min_size then
130            return min_size - icon_size
131        elseif icon_size > max_size then
132            return icon_size - max_size
133        end
134        return 0
135    elseif kind == "Threshold" then
136        if icon_size < size - threshold then
137            return min_size - icon_size
138        elseif icon_size > size + threshold then
139            return icon_size - max_size
140        end
141        return 0
142    end
143
144    return 0xffffffff -- Any large number will do.
145end
146
147local lookup_icon = function(self, icon_name, icon_size)
148    local checked_already = {}
149    for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
150        for _, basedir in ipairs(self.base_directories) do
151            for _, ext in ipairs(self.extensions) do
152                if directory_matches_size(self, subdir, icon_size) then
153                    local filename = string.format("%s/%s/%s/%s.%s",
154                                                   basedir, self.icon_theme_name, subdir,
155                                                   icon_name, ext)
156                    if gfs.file_readable(filename) then
157                        return filename
158                    else
159                        checked_already[filename] = true
160                    end
161                end
162            end
163        end
164    end
165
166    local minimal_size = 0xffffffff -- Any large number will do.
167    local closest_filename = nil
168    for _, subdir in ipairs(self.index_theme:get_subdirectories()) do
169        local dist = directory_size_distance(self, subdir, icon_size)
170        if dist < minimal_size then
171            for _, basedir in ipairs(self.base_directories) do
172                for _, ext in ipairs(self.extensions) do
173                    local filename = string.format("%s/%s/%s/%s.%s",
174                                                   basedir, self.icon_theme_name, subdir,
175                                                   icon_name, ext)
176                    if not checked_already[filename] then
177                        if gfs.file_readable(filename) then
178                            closest_filename = filename
179                            minimal_size = dist
180                        end
181                    end
182                end
183            end
184        end
185    end
186    return closest_filename
187end
188
189local find_icon_path_helper  -- Gets called recursively.
190find_icon_path_helper = function(self, icon_name, icon_size)
191    local filename = lookup_icon(self, icon_name, icon_size)
192    if filename then
193        return filename
194    end
195
196    for _, parent in ipairs(self.index_theme:get_inherits()) do
197        local parent_icon_theme = icon_theme(parent, self.base_directories)
198        filename = find_icon_path_helper(parent_icon_theme, icon_name, icon_size)
199        if filename then
200            return filename
201        end
202    end
203
204    return nil
205end
206
207local lookup_fallback_icon = function(self, icon_name)
208    for _, dir in ipairs(self.base_directories) do
209        for _, ext in ipairs(self.extensions) do
210            local filename = string.format("%s/%s.%s",
211                                           dir,
212                                           icon_name, ext)
213            if gfs.file_readable(filename) then
214                return filename
215            end
216        end
217    end
218    return nil
219end
220
221---  Look up an image file based on a given icon name and/or a preferable size.
222-- @deprecated menubar.icon_theme:find_icon_path
223-- @tparam string icon_name Icon name to be looked up
224-- @tparam number icon_size Prefereable icon size
225-- @treturn string Absolute path to the icon file, or nil if not found
226function icon_theme:find_icon_path(icon_name, icon_size)
227    icon_size = icon_size or 16
228    if not icon_name or icon_name == "" then
229        return nil
230    end
231
232    local filename = find_icon_path_helper(self, icon_name, icon_size)
233    if filename then
234        return filename
235    end
236
237    if self.icon_theme_name ~= "hicolor" then
238        filename = find_icon_path_helper(icon_theme("hicolor", self.base_directories), icon_name, icon_size)
239        if filename then
240            return filename
241        end
242    end
243
244    return lookup_fallback_icon(self, icon_name)
245end
246
247icon_theme.mt.__call = function(_, ...)
248    return icon_theme.new(...)
249end
250
251return setmetatable(icon_theme, icon_theme.mt)
252
253-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
254