naughty: text-icon separator uses margin now
[awesome.git] / lib / naughty.lua.in
blobb44d16093ef35fee15cc88feee67177a1947a18d
1 ----------------------------------------------------------------------------
2 -- @author koniu <gkusnierz@gmail.com>
3 -- @copyright 2008 koniu
4 -- @release @AWESOME_VERSION@
5 ----------------------------------------------------------------------------
7 -- Package environment
8 local pairs = pairs
9 local table = table
10 local wibox = wibox
11 local image = image
12 local hooks = require("awful.hooks")
13 local string = string
14 local widget = widget
15 local button = button
16 local capi = { screen = screen }
17 local bt = require("beautiful")
18 local beautiful = bt.get()
20 --- Notification library
21 module("naughty")
23 --- Naughty configuration - a table containing common/default popup settings.
24 -- You can override some of these for individual popups using args to notify().
25 -- @name config
26 -- @field timeout Number of seconds after which popups disappear.
27 -- Set to 0 for no timeout. Default: 5
28 -- @field screen Screen on which the popups will appear number. Default: 1
29 -- @field position Corner of the workarea the popups will appear.
30 -- Valid values: 'top_right', 'top_left', 'bottom_right', 'bottom_left'.
31 -- Default: 'top_right'
32 -- @field margin Space between popups and edge of the workarea. Default: 4
33 -- @field height Height of a single line of text. Default: 16
34 -- @field width Width of a popup. Default: 300
35 -- @field gap Spacing between popups. Default: 1
36 -- @field ontop Boolean forcing popups to display on top. Default: true
37 -- @field font Popup font. Default: beautiful.font or "Verdana 8"
38 -- @field icon Popup icon. Default: nil
39 -- @field icon_size Size of the icon in pixels. Default: 16
40 -- @field fg Foreground color. Default: beautiful.fg_focus or '#ffffff'
41 -- @field bg Background color. Default: beautiful.bg_focus or '#535d6c'
42 -- @field border_color Border color.
43 -- Default: beautiful.border_focus or '#535d6c'
44 -- @field border_width Border width. Default: 1
45 -- @field hover_timeout Delay in seconds after which hovered popup disappears.
46 -- Default: nil
47 -- @class table
49 config = {}
50 config.timeout = 5
51 config.screen = 1
52 config.position = "top_right"
53 config.margin = 4
54 config.height = 16
55 config.width = 300
56 config.gap = 1
57 config.ontop = true
58 config.font = beautiful.font or "Verdana 8"
59 config.icon = nil
60 config.icon_size = 16
61 config.fg = beautiful.fg_focus or '#ffffff'
62 config.bg = beautiful.bg_focus or '#535d6c'
63 config.border_color = beautiful.border_focus or '#535d6c'
64 config.border_width = 1
65 config.hover_timeout = nil
67 --- Index of notifications. See config table for valid 'position' values.
68 -- Each element is a table consisting of:
69 -- @field box Wibox object containing the popup
70 -- @field height Popup height
71 -- @field die Function to be executed on timeout
72 -- @name notifications[position]
73 -- @class table
75 notifications = {
76 top_left = {},
77 top_right = {},
78 bottom_left = {},
79 bottom_right = {},
82 --- Evaluate desired position of the notification by index - internal
83 -- @param idx Index of the notification
84 -- @param position top_right | top_left | bottom_right | bottom_left
85 -- @param height Popup height
86 -- @return Absolute position in {x, y} dictionary
88 local function get_offset(idx, position, height)
89 local ws = capi.screen[config.screen].workarea
90 local v = {}
92 -- calculate x
93 if position:match("left") then
94 v.x = ws.x + config.margin
95 else
96 v.x = ws.x + ws.width - (config.width + config.border_width*2 + config.margin)
97 end
99 -- calculate existing popups' height
100 local existing = 0
101 for i = 1, idx-1, 1 do
102 existing = existing + notifications[position][i].height + config.gap + config.border_width*2
105 -- calculate y
106 if position:match("top") then
107 v.y = ws.y + config.margin + existing
108 else
109 v.y = ws.y + ws.height - (config.margin + config.border_width + height + existing)
112 -- if positioned outside workarea, destroy oldest popup and recalculate
113 if v.y + height > ws.y + ws.height or v.y < ws.y then
114 idx = idx - 1
115 destroy(notifications[position][1])
116 v = get_offset(idx, position, height)
119 return v
122 --- Re-arrange notifications according to their position and index - internal
123 -- @return None
124 local function arrange()
125 for p,pos in pairs(notifications) do
126 for i,notification in pairs(notifications[p]) do
127 local offset = get_offset(i, p, notification.height)
128 notification.box:geometry({ x = offset.x, y = offset.y, width = config.width, height = notification.height })
129 notification.idx = i
134 --- Destroy notification by index
135 -- @param idx Index of the notification
136 -- @param position One of 4 keys in notification dictionary: top_right, top_left, bottom_right, bottom_left
137 -- @return True if the popup was successfully destroyed, nil otherwise
138 function destroy(notification)
139 if notification then
140 notification.box.screen = nil
141 hooks.timer.unregister(notification.die)
142 table.remove(notifications[notification.position], notification.idx)
143 arrange()
144 return true
148 --- Create notification. args is a dictionary of optional arguments. For more information and defaults see respective fields in config table.
149 -- @param text Text of the notification
150 -- @param timeout Time in seconds after which popup expires
151 -- @param title Title of the notification
152 -- @param position Corner of the workarea the popups will appear
153 -- @param icon Path to icon
154 -- @param icon_size Desired icon size in px
155 -- @param fg Foreground color
156 -- @param bg Background color
157 -- @param screen Target screen for the notification
158 -- @param ontop Target screen for the notification
159 -- @param run Function to run on left click
160 -- @usage naughty.notify({ title = 'Achtung!', text = 'You\'re idling', timeout = 0 })
161 function notify(args)
162 -- gather variables together
163 local timeout = args.timeout or config.timeout
164 local icon = args.icon or config.icon
165 local icon_size = args.icon_size or config.icon_size
166 local text = args.text or ""
167 local screen = args.screen or config.screen
168 local ontop = args.ontop or config.ontop
170 local notification = {}
171 notification.position = args.position or config.position
172 notification.idx = #notifications[notification.position] + 1
174 local title = ""
175 if args.title then title = args.title .. "\n" end
177 -- hook destroy
178 local die = function () destroy(notification) end
179 hooks.timer.register(timeout, die)
180 notification.die = die
182 local run = args.run or die
184 local hover_destroy = function ()
185 if config.hover_timeout == 0 then die()
186 else hooks.timer.register(config.hover_timeout, die) end
189 -- create textbox
190 local textbox = widget({ type = "textbox", name = "text", align = "flex" })
191 textbox.margin = 20
192 textbox:buttons({ button({ }, 1, run),
193 button({ }, 3, die) })
194 textbox.text = string.format('<margin left="10"/><span font_desc="%s"><b>%s</b>%s</span>',
195 config.font, title, text)
196 if config.hover_timeout then textbox.mouse_enter = hover_destroy end
198 -- create iconbox
199 local iconbox = nil
200 if icon then
201 iconbox = widget({ type = "imagebox", name = "icon", align = "left" })
202 iconbox:buttons({ button({ }, 1, run),
203 button({ }, 3, die) })
204 local img = image(icon)
205 if icon_size then
206 img = img:crop_and_scale(0,0,img.height,img.width,icon_size,icon_size)
207 iconbox.resize = false
209 iconbox.image = img
210 if config.hover_timeout then iconbox.mouse_enter = hover_destroy end
213 -- create container wibox
214 notification.box = wibox({ name = "not" .. notification.idx,
215 position = "floating",
216 fg = args.fg or config.fg,
217 bg = args.bg or config.bg,
218 border_color = config.border_color,
219 border_width = config.border_width })
221 -- position the wibox
222 local lines = 1; for i in string.gmatch(title..text, "\n") do lines = lines + 1 end
223 if iconbox and iconbox.image.height > lines * config.height then
224 notification.height = iconbox.image.height
225 else
226 notification.height = lines * config.height end
227 local offset = get_offset(notification.idx, notification.position, notification.height)
228 notification.box:geometry({ width = config.width,
229 height = notification.height,
230 x = offset.x,
231 y = offset.y })
232 notification.box.ontop = ontop
233 notification.box.screen = screen
235 -- populate widgets
236 notification.box.widgets = { iconbox, textbox }
238 -- insert the notification to the table
239 table.insert(notifications[notification.position],notification)
242 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80