awful.tag.attached_connect_signal: Simplify
[awesome.git] / lib / awful / tag.lua.in
blob98ccd4d5be77edd0fa9423c642b36fc49e4d6fe2
1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local util = require("awful.util")
9 local tostring = tostring
10 local pairs = pairs
11 local ipairs = ipairs
12 local table = table
13 local setmetatable = setmetatable
14 local capi =
16 tag = tag,
17 screen = screen,
18 mouse = mouse,
19 client = client
22 --- Useful functions for tag manipulation.
23 -- awful.tag
24 local tag = { mt = {} }
26 -- Private data
27 local data = {}
28 data.history = {}
29 data.tags = setmetatable({}, { __mode = 'k' })
31 -- History functions
32 tag.history = {}
33 tag.history.limit = 20
35 --- Move a tag to an absolute position in the screen[]:tags() table.
36 -- @param new_index Integer absolute position in the table to insert.
37 function tag.move(new_index, target_tag)
38 local target_tag = target_tag or tag.selected()
39 local scr = target_tag.screen
40 local tmp_tags = capi.screen[scr]:tags()
42 if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
43 return
44 end
46 for i, t in ipairs(tmp_tags) do
47 if t == target_tag then
48 table.remove(tmp_tags, i)
49 break
50 end
51 end
53 table.insert(tmp_tags, new_index, target_tag)
54 capi.screen[scr]:tags(tmp_tags)
55 end
57 --- Add a tag.
58 -- @param name The tag name, a string
59 -- @param props The tags properties, a table
60 -- @return The created tag
61 function tag.add(name, props)
62 local properties = props or {}
63 local newtag = capi.tag{name = name}
64 newtag.screen = properties.screen or capi.mouse.screen
66 for k, v in pairs(properties) do
67 tag.setproperty(newtag, k, v)
68 end
70 return newtag
71 end
73 --- Create a set of tags and attach it to a screen.
74 -- @param names The tag name, in a table
75 -- @param screen The tag screen, or 1 if not set.
76 -- @param layout The layout or layout table to set for this tags by default.
77 -- @return A table with all created tags.
78 function tag.new(names, screen, layout)
79 local screen = screen or 1
80 local tags = {}
81 for id, name in ipairs(names) do
82 table.insert(tags, id, tag.add(name, {screen = screen,
83 layout = (layout and layout[id]) or
84 layout}))
85 -- Select the first tag.
86 if id == 1 then
87 tags[id].selected = true
88 end
89 end
91 return tags
92 end
94 --- Find a suitable fallback tag.
95 -- @param screen The screen number to look for a tag on. [mouse.screen]
96 -- @param target A table of tags we consider unacceptable. [selectedlist(scr)]
97 function tag.find_fallback(screen, invalids)
98 local scr = screen or capi.mouse.screen
99 local t = invalids or tag.selectedlist(scr)
101 for _, v in pairs(capi.screen[scr]:tags()) do
102 if not util.table.hasitem(t, v) then return v end
106 --- Delete a tag.
107 -- @param target_tag Optional tag object to delete. [selected()]
108 -- @param fallback_tag Tag to assign stickied tags to. [~selected()]
109 -- @return Returns true if the tag is successfully deleted, nil otherwise.
110 -- If there are no clients exclusively on this tag then delete it. Any
111 -- stickied clients are assigned to the optional 'fallback_tag'.
112 -- If after deleting the tag there is no selected tag, try and restore from
113 -- history or select the first tag on the screen.
114 function tag.delete(target_tag, fallback_tag)
115 -- abort if no tag is passed or currently selected
116 local target_tag = target_tag or tag.selected()
117 if target_tag == nil then return end
119 local ntags = #capi.screen[target_tag.screen]:tags()
120 local target_scr = target_tag.screen
122 -- We can't use the target tag as a fallback.
123 local fallback_tag = fallback_tag
124 if fallback_tag == target_tag then return end
126 -- No fallback_tag provided, try and get one.
127 if fallback_tag == nil then
128 fallback_tag = tag.find_fallback(target_scr, {target_tag})
131 -- Abort if we would have un-tagged clients.
132 local clients = target_tag:clients()
133 if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
135 -- Move the clients we can off of this tag.
136 for _, c in pairs(clients) do
138 -- If a client has only this tag, or stickied clients with
139 -- nowhere to go, abort.
140 if (not c.sticky and #c:tags() == 1) or
141 (c.sticky and fallback_tag == nil) then
142 return
143 else
144 c:tags({fallback_tag})
148 -- delete the tag
149 target_tag.screen = nil
151 -- If no tags are visible, try and view one.
152 if tag.selected(target_scr) == nil and ntags > 0 then
153 tag.history.restore()
154 if tag.selected(target_scr) == nil then
155 capi.screen[target_scr]:tags()[1].selected = true
159 return true
162 --- Update the tag history.
163 -- @param obj Screen object.
164 function tag.history.update(obj)
165 local s = obj.index
166 local curtags = tag.selectedlist(s)
167 -- create history table
168 if not data.history[s] then
169 data.history[s] = {}
170 else
171 if data.history[s].current then
172 -- Check that the list is not identical
173 local identical = true
174 for idx, _tag in ipairs(data.history[s].current) do
175 if curtags[idx] ~= _tag then
176 identical = false
177 break
181 -- Do not update history the table are identical
182 if identical then return end
185 -- Limit history
186 if #data.history[s] >= tag.history.limit then
187 for i = tag.history.limit, #data.history[s] do
188 data.history[s][i] = nil
193 -- store previously selected tags in the history table
194 table.insert(data.history[s], 1, data.history[s].current)
195 data.history[s].previous = data.history[s][1]
196 -- store currently selected tags
197 data.history[s].current = setmetatable(curtags, { __mode = 'v' })
200 --- Revert tag history.
201 -- @param screen The screen number.
202 -- @param idx Index in history. Defaults to "previous" which is a special index
203 -- toggling between last two selected sets of tags. Number (eg 1) will go back
204 -- to the given index in history.
205 function tag.history.restore(screen, idx)
206 local s = screen or capi.mouse.screen
207 local i = idx or "previous"
208 local sel = tag.selectedlist(s)
209 -- do nothing if history empty
210 if not data.history[s] or not data.history[s][i] then return end
211 -- if all tags been deleted, try next entry
212 if #data.history[s][i] == 0 then
213 if i == "previous" then i = 0 end
214 tag.history.restore(s, i + 1)
215 return
217 -- deselect all tags
218 tag.viewnone(s)
219 -- select tags from the history entry
220 for _, t in ipairs(data.history[s][i]) do
221 t.selected = true
223 -- update currently selected tags table
224 data.history[s].current = data.history[s][i]
225 -- store previously selected tags
226 data.history[s].previous = setmetatable(sel, { __mode = 'v' })
227 -- remove the reverted history entry
228 if i ~= "previous" then table.remove(data.history[s], i) end
231 --- Get a list of all tags on a screen
232 -- @param s Screen number
233 -- @return A table with all available tags
234 function tag.gettags(s)
235 return capi.screen[s]:tags()
238 --- Return a table with all visible tags
239 -- @param s Screen number.
240 -- @return A table with all selected tags.
241 function tag.selectedlist(s)
242 local screen = s or capi.mouse.screen
243 local tags = tag.gettags(screen)
244 local vtags = {}
245 for i, t in pairs(tags) do
246 if t.selected then
247 vtags[#vtags + 1] = t
250 return vtags
253 --- Return only the first visible tag.
254 -- @param s Screen number.
255 function tag.selected(s)
256 return tag.selectedlist(s)[1]
259 --- Set master width factor.
260 -- @param mwfact Master width factor.
261 function tag.setmwfact(mwfact, t)
262 local t = t or tag.selected()
263 if mwfact >= 0 and mwfact <= 1 then
264 tag.setproperty(t, "mwfact", mwfact)
268 --- Increase master width factor.
269 -- @param add Value to add to master width factor.
270 function tag.incmwfact(add, t)
271 tag.setmwfact(tag.getmwfact(t) + add, t)
274 --- Get master width factor.
275 -- @param t Optional tag.
276 function tag.getmwfact(t)
277 local t = t or tag.selected()
278 return tag.getproperty(t, "mwfact") or 0.5
281 --- Set the number of master windows.
282 -- @param nmaster The number of master windows.
283 -- @param t Optional tag.
284 function tag.setnmaster(nmaster, t)
285 local t = t or tag.selected()
286 if nmaster >= 0 then
287 tag.setproperty(t, "nmaster", nmaster)
291 --- Get the number of master windows.
292 -- @param t Optional tag.
293 function tag.getnmaster(t)
294 local t = t or tag.selected()
295 return tag.getproperty(t, "nmaster") or 1
298 --- Increase the number of master windows.
299 -- @param add Value to add to number of master windows.
300 function tag.incnmaster(add, t)
301 tag.setnmaster(tag.getnmaster(t) + add, t)
305 --- Set the tag icon
306 -- @param icon the icon to set, either path or image object
307 -- @param tag the tag
308 function tag.seticon(icon, _tag)
309 local _tag = _tag or tag.selected()
310 tag.setproperty(_tag, "icon", icon)
313 --- Get the tag icon
314 -- @param t the tag
315 function tag.geticon(_tag)
316 local _tag = _tag or tag.selected()
317 return tag.getproperty(_tag, "icon")
320 --- Set number of column windows.
321 -- @param ncol The number of column.
322 function tag.setncol(ncol, t)
323 local t = t or tag.selected()
324 if ncol >= 1 then
325 tag.setproperty(t, "ncol", ncol)
329 --- Get number of column windows.
330 -- @param t Optional tag.
331 function tag.getncol(t)
332 local t = t or tag.selected()
333 return tag.getproperty(t, "ncol") or 1
336 --- Increase number of column windows.
337 -- @param add Value to add to number of column windows.
338 function tag.incncol(add, t)
339 tag.setncol(tag.getncol(t) + add, t)
342 --- View no tag.
343 -- @param Optional screen number.
344 function tag.viewnone(screen)
345 local tags = tag.gettags(screen or capi.mouse.screen)
346 for i, t in pairs(tags) do
347 t.selected = false
351 --- View a tag by its taglist index.
352 -- @param i The relative index to see.
353 -- @param screen Optional screen number.
354 function tag.viewidx(i, screen)
355 local screen = screen or capi.mouse.screen
356 local tags = tag.gettags(screen)
357 local showntags = {}
358 for k, t in ipairs(tags) do
359 if not tag.getproperty(t, "hide") then
360 table.insert(showntags, t)
363 local sel = tag.selected(screen)
364 tag.viewnone(screen)
365 for k, t in ipairs(showntags) do
366 if t == sel then
367 showntags[util.cycle(#showntags, k + i)].selected = true
370 capi.screen[screen]:emit_signal("tag::history::update")
373 --- Get a tag's index in the gettags() table.
374 -- @param query_tag The tag object to find. [selected()]
375 -- @return The index of the tag, nil if the tag is not found.
376 function tag.getidx(query_tag)
377 local query_tag = query_tag or tag.selected()
378 if query_tag == nil then return end
380 for i, t in ipairs(tag.gettags(query_tag.screen)) do
381 if t == query_tag then
382 return i
387 --- View next tag. This is the same as tag.viewidx(1).
388 -- @param screen The screen number.
389 function tag.viewnext(screen)
390 return tag.viewidx(1, screen)
393 --- View previous tag. This is the same a tag.viewidx(-1).
394 -- @param screen The screen number.
395 function tag.viewprev(screen)
396 return tag.viewidx(-1, screen)
399 --- View only a tag.
400 -- @param t The tag object.
401 function tag.viewonly(t)
402 local tags = tag.gettags(tag.getscreen(t))
403 -- First, untag everyone except the viewed tag.
404 for _, _tag in pairs(tags) do
405 if _tag ~= t then
406 _tag.selected = false
409 -- Then, set this one to selected.
410 -- We need to do that in 2 operations so we avoid flickering and several tag
411 -- selected at the same time.
412 t.selected = true
413 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
416 --- View only a set of tags.
417 -- @param tags A table with tags to view only.
418 -- @param screen Optional screen number of the tags.
419 function tag.viewmore(tags, screen)
420 local screen_tags = tag.gettags(screen or capi.mouse.screen)
421 for _, _tag in ipairs(screen_tags) do
422 if not util.table.hasitem(tags, _tag) then
423 _tag.selected = false
426 for _, _tag in ipairs(tags) do
427 _tag.selected = true
429 capi.screen[screen]:emit_signal("tag::history::update")
432 --- Toggle selection of a tag
433 -- @param tag Tag to be toggled
434 function tag.viewtoggle(t)
435 t.selected = not t.selected
436 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
439 --- Get tag data table.
440 -- @param tag The Tag.
441 -- @return The data table.
442 function tag.getdata(_tag)
443 return data.tags[_tag]
446 --- Get a tag property.
447 -- @param tag The tag.
448 -- @param prop The property name.
449 -- @return The property.
450 function tag.getproperty(_tag, prop)
451 if data.tags[_tag] then
452 return data.tags[_tag][prop]
456 --- Set a tag property.
457 -- This properties are internal to awful. Some are used to draw taglist, or to
458 -- handle layout, etc.
459 -- @param tag The tag.
460 -- @param prop The property name.
461 -- @param value The value.
462 function tag.setproperty(_tag, prop, value)
463 if not data.tags[_tag] then
464 data.tags[_tag] = {}
466 data.tags[_tag][prop] = value
467 _tag:emit_signal("property::" .. prop)
470 --- Tag a client with the set of current tags.
471 -- @param c The client to tag.
472 -- @param startup Optional: don't do anything if true.
473 function tag.withcurrent(c, startup)
474 if startup ~= true then
475 if #c:tags() == 0 then
476 c:tags(tag.selectedlist(c.screen))
481 local function attached_connect_signal_screen(screen, sig, func)
482 capi.tag.connect_signal(sig, function(_tag, ...)
483 if tag.getscreen(_tag) == screen then
484 func(_tag)
486 end)
489 --- Add a signal to all attached tag and all tag that will be attached in the
490 -- future. When a tag is detach from the screen, its signal is removed.
491 -- @param screen The screen concerned, or all if nil.
492 function tag.attached_connect_signal(screen, ...)
493 if screen then
494 attached_connect_signal_screen(screen, ...)
495 else
496 capi.tag.connect_signal(...)
500 -- Register standards signals
501 capi.client.connect_signal("manage", function(c, startup)
502 -- If we are not managing this application at startup,
503 -- move it to the screen where the mouse is.
504 -- We only do it for "normal" windows (i.e. no dock, etc).
505 if not startup and c.type ~= "desktop" and c.type ~= "dock" then
506 if c.transient_for then
507 c.screen = c.transient_for.screen
508 if not c.sticky then
509 c:tags(c.transient_for:tags())
511 else
512 c.screen = capi.mouse.screen
515 c:connect_signal("property::screen", tag.withcurrent)
516 end)
518 capi.client.connect_signal("manage", tag.withcurrent)
520 capi.tag.add_signal("property::hide")
521 capi.tag.add_signal("property::icon")
522 capi.tag.add_signal("property::layout")
523 capi.tag.add_signal("property::mwfact")
524 capi.tag.add_signal("property::ncol")
525 capi.tag.add_signal("property::nmaster")
526 capi.tag.add_signal("property::windowfact")
528 for s = 1, capi.screen.count() do
529 capi.screen[s]:add_signal("tag::history::update")
530 capi.screen[s]:connect_signal("tag::history::update", tag.history.update)
533 function tag.mt:__call(...)
534 return tag.new(...)
537 return setmetatable(tag, tag.mt)
539 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80