Fix the call to tag.history.restore from tag.delete
[awesome.git] / lib / awful / tag.lua.in
blob4067963daff3dd8ae0184d0bdae8c5b65122fb92
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,
20 root = root
23 --- Useful functions for tag manipulation.
24 -- awful.tag
25 local tag = { mt = {} }
27 -- Private data
28 local data = {}
29 data.history = {}
30 data.tags = setmetatable({}, { __mode = 'k' })
32 -- History functions
33 tag.history = {}
34 tag.history.limit = 20
36 --- Move a tag to an absolute position in the screen[]:tags() table.
37 -- @param new_index Integer absolute position in the table to insert.
38 -- @param target_tag The tag that should be moved. If null, the currently
39 -- selected tag is used.
40 function tag.move(new_index, target_tag)
41 local target_tag = target_tag or tag.selected()
42 local scr = tag.getscreen(target_tag)
43 local tmp_tags = tag.gettags(scr)
45 if (not new_index) or (new_index < 1) or (new_index > #tmp_tags) then
46 return
47 end
49 for i, t in ipairs(tmp_tags) do
50 if t == target_tag then
51 table.remove(tmp_tags, i)
52 break
53 end
54 end
56 table.insert(tmp_tags, new_index, target_tag)
58 for i, tmp_tag in ipairs(tmp_tags) do
59 tag.setproperty(tmp_tag, "index", i)
60 tag.setscreen(tmp_tag, scr)
61 end
62 end
64 --- Add a tag.
65 -- @param name The tag name, a string
66 -- @param props The tags properties, a table
67 -- @return The created tag
68 function tag.add(name, props)
69 local properties = props or {}
70 local newtag = capi.tag{ name = name, activated = true }
71 properties.screen = properties.screen or capi.mouse.screen
73 for k, v in pairs(properties) do
74 tag.setproperty(newtag, k, v)
75 end
77 return newtag
78 end
80 --- Create a set of tags and attach it to a screen.
81 -- @param names The tag name, in a table
82 -- @param screen The tag screen, or 1 if not set.
83 -- @param layout The layout or layout table to set for this tags by default.
84 -- @return A table with all created tags.
85 function tag.new(names, screen, layout)
86 local screen = screen or 1
87 local tags = {}
88 for id, name in ipairs(names) do
89 table.insert(tags, id, tag.add(name, {screen = screen,
90 layout = (layout and layout[id]) or
91 layout}))
92 -- Select the first tag.
93 if id == 1 then
94 tags[id].selected = true
95 end
96 end
98 return tags
99 end
101 --- Find a suitable fallback tag.
102 -- @param screen The screen number to look for a tag on. [mouse.screen]
103 -- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
104 function tag.find_fallback(screen, invalids)
105 local scr = screen or capi.mouse.screen
106 local t = invalids or tag.selectedlist(scr)
108 for _, v in pairs(tag.gettags(scr)) do
109 if not util.table.hasitem(t, v) then return v end
113 --- Delete a tag.
114 -- @param target_tag Optional tag object to delete. [selected()]
115 -- @param fallback_tag Tag to assign stickied tags to. [~selected()]
116 -- @return Returns true if the tag is successfully deleted, nil otherwise.
117 -- If there are no clients exclusively on this tag then delete it. Any
118 -- stickied clients are assigned to the optional 'fallback_tag'.
119 -- If after deleting the tag there is no selected tag, try and restore from
120 -- history or select the first tag on the screen.
121 function tag.delete(target_tag, fallback_tag)
122 -- abort if no tag is passed or currently selected
123 local target_tag = target_tag or tag.selected()
124 if target_tag == nil then return end
126 local target_scr = tag.getscreen(target_tag)
127 local ntags = #tag.gettags(target_scr)
129 -- We can't use the target tag as a fallback.
130 local fallback_tag = fallback_tag
131 if fallback_tag == target_tag then return end
133 -- No fallback_tag provided, try and get one.
134 if fallback_tag == nil then
135 fallback_tag = tag.find_fallback(target_scr, {target_tag})
138 -- Abort if we would have un-tagged clients.
139 local clients = target_tag:clients()
140 if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
142 -- Move the clients we can off of this tag.
143 for _, c in pairs(clients) do
145 -- If a client has only this tag, or stickied clients with
146 -- nowhere to go, abort.
147 if (not c.sticky and #c:tags() == 1) or
148 (c.sticky and fallback_tag == nil) then
149 return
150 else
151 c:tags({fallback_tag})
155 -- delete the tag
156 data.tags[target_tag].screen = nil
157 target_tag.activated = false
159 -- If no tags are visible, try and view one.
160 if tag.selected(target_scr) == nil and ntags > 0 then
161 tag.history.restore(nil, 1)
162 if tag.selected(target_scr) == nil then
163 tag.gettags(target_scr)[1].selected = true
167 return true
170 --- Update the tag history.
171 -- @param obj Screen object.
172 function tag.history.update(obj)
173 local s = obj.index
174 local curtags = tag.selectedlist(s)
175 -- create history table
176 if not data.history[s] then
177 data.history[s] = {}
178 else
179 if data.history[s].current then
180 -- Check that the list is not identical
181 local identical = true
182 for idx, _tag in ipairs(data.history[s].current) do
183 if curtags[idx] ~= _tag then
184 identical = false
185 break
189 -- Do not update history the table are identical
190 if identical then return end
193 -- Limit history
194 if #data.history[s] >= tag.history.limit then
195 for i = tag.history.limit, #data.history[s] do
196 data.history[s][i] = nil
201 -- store previously selected tags in the history table
202 table.insert(data.history[s], 1, data.history[s].current)
203 data.history[s].previous = data.history[s][1]
204 -- store currently selected tags
205 data.history[s].current = setmetatable(curtags, { __mode = 'v' })
208 --- Revert tag history.
209 -- @param screen The screen number.
210 -- @param idx Index in history. Defaults to "previous" which is a special index
211 -- toggling between last two selected sets of tags. Number (eg 1) will go back
212 -- to the given index in history.
213 function tag.history.restore(screen, idx)
214 local s = screen or capi.mouse.screen
215 local i = idx or "previous"
216 local sel = tag.selectedlist(s)
217 -- do nothing if history empty
218 if not data.history[s] or not data.history[s][i] then return end
219 -- if all tags been deleted, try next entry
220 if #data.history[s][i] == 0 then
221 if i == "previous" then i = 0 end
222 tag.history.restore(s, i + 1)
223 return
225 -- deselect all tags
226 tag.viewnone(s)
227 -- select tags from the history entry
228 for _, t in ipairs(data.history[s][i]) do
229 t.selected = true
231 -- update currently selected tags table
232 data.history[s].current = data.history[s][i]
233 -- store previously selected tags
234 data.history[s].previous = setmetatable(sel, { __mode = 'v' })
235 -- remove the reverted history entry
236 if i ~= "previous" then table.remove(data.history[s], i) end
239 --- Get a list of all tags on a screen
240 -- @param s Screen number
241 -- @return A table with all available tags
242 function tag.gettags(s)
243 local tags = {}
244 for i, t in ipairs(root.tags()) do
245 if tag.getscreen(t) == s then
246 table.insert(tags, t)
250 local without_index = 0
251 for _, t in ipairs(tags) do
252 if not tag.getproperty(t, "index") then
253 without_index = without_index + 1
256 if without_index > 0 then
257 for _, t in ipairs(tags) do
258 if not tag.getproperty(t, "index") then
259 tag.setproperty(t, "index", (#tags - without_index + 1))
260 without_index = without_index - 1
265 table.sort(tags, function(a, b) return tag.getproperty(a, "index") < tag.getproperty(b, "index") end)
266 return tags
269 --- Set a tag's screen
270 -- @param t tag object
271 -- @param s Screen number
272 function tag.setscreen(t, s)
273 tag.setproperty(t, "screen", s)
276 --- Get a tag's screen
277 -- @param t tag object
278 -- @return Screen number
279 function tag.getscreen(t)
280 return tag.getproperty(t, "screen")
283 --- Return a table with all visible tags
284 -- @param s Screen number.
285 -- @return A table with all selected tags.
286 function tag.selectedlist(s)
287 local screen = s or capi.mouse.screen
288 local tags = tag.gettags(screen)
289 local vtags = {}
290 for i, t in pairs(tags) do
291 if t.selected then
292 vtags[#vtags + 1] = t
295 return vtags
298 --- Return only the first visible tag.
299 -- @param s Screen number.
300 function tag.selected(s)
301 return tag.selectedlist(s)[1]
304 --- Set master width factor.
305 -- @param mwfact Master width factor.
306 -- @param t The tag to modify, if null tag.selected() is used.
307 function tag.setmwfact(mwfact, t)
308 local t = t or tag.selected()
309 if mwfact >= 0 and mwfact <= 1 then
310 tag.setproperty(t, "mwfact", mwfact)
314 --- Increase master width factor.
315 -- @param add Value to add to master width factor.
316 -- @param t The tag to modify, if null tag.selected() is used.
317 function tag.incmwfact(add, t)
318 tag.setmwfact(tag.getmwfact(t) + add, t)
321 --- Get master width factor.
322 -- @param t Optional tag.
323 function tag.getmwfact(t)
324 local t = t or tag.selected()
325 return tag.getproperty(t, "mwfact") or 0.5
328 --- Set the number of master windows.
329 -- @param nmaster The number of master windows.
330 -- @param t Optional tag.
331 function tag.setnmaster(nmaster, t)
332 local t = t or tag.selected()
333 if nmaster >= 0 then
334 tag.setproperty(t, "nmaster", nmaster)
338 --- Get the number of master windows.
339 -- @param t Optional tag.
340 function tag.getnmaster(t)
341 local t = t or tag.selected()
342 return tag.getproperty(t, "nmaster") or 1
345 --- Increase the number of master windows.
346 -- @param add Value to add to number of master windows.
347 -- @param t The tag to modify, if null tag.selected() is used.
348 function tag.incnmaster(add, t)
349 tag.setnmaster(tag.getnmaster(t) + add, t)
353 --- Set the tag icon
354 -- @param icon the icon to set, either path or image object
355 -- @param _tag the tag
356 function tag.seticon(icon, _tag)
357 local _tag = _tag or tag.selected()
358 tag.setproperty(_tag, "icon", icon)
361 --- Get the tag icon
362 -- @param _tag the tag
363 function tag.geticon(_tag)
364 local _tag = _tag or tag.selected()
365 return tag.getproperty(_tag, "icon")
368 --- Set number of column windows.
369 -- @param ncol The number of column.
370 -- @param t The tag to modify, if null tag.selected() is used.
371 function tag.setncol(ncol, t)
372 local t = t or tag.selected()
373 if ncol >= 1 then
374 tag.setproperty(t, "ncol", ncol)
378 --- Get number of column windows.
379 -- @param t Optional tag.
380 function tag.getncol(t)
381 local t = t or tag.selected()
382 return tag.getproperty(t, "ncol") or 1
385 --- Increase number of column windows.
386 -- @param add Value to add to number of column windows.
387 -- @param t The tag to modify, if null tag.selected() is used.
388 function tag.incncol(add, t)
389 tag.setncol(tag.getncol(t) + add, t)
392 --- View no tag.
393 -- @param Optional screen number.
394 function tag.viewnone(screen)
395 local tags = tag.gettags(screen or capi.mouse.screen)
396 for i, t in pairs(tags) do
397 t.selected = false
401 --- View a tag by its taglist index.
402 -- @param i The relative index to see.
403 -- @param screen Optional screen number.
404 function tag.viewidx(i, screen)
405 local screen = screen or capi.mouse.screen
406 local tags = tag.gettags(screen)
407 local showntags = {}
408 for k, t in ipairs(tags) do
409 if not tag.getproperty(t, "hide") then
410 table.insert(showntags, t)
413 local sel = tag.selected(screen)
414 tag.viewnone(screen)
415 for k, t in ipairs(showntags) do
416 if t == sel then
417 showntags[util.cycle(#showntags, k + i)].selected = true
420 capi.screen[screen]:emit_signal("tag::history::update")
423 --- Get a tag's index in the gettags() table.
424 -- @param query_tag The tag object to find. [selected()]
425 -- @return The index of the tag, nil if the tag is not found.
426 function tag.getidx(query_tag)
427 local query_tag = query_tag or tag.selected()
428 if query_tag == nil then return end
430 for i, t in ipairs(tag.gettags(tag.getscreen(query_tag))) do
431 if t == query_tag then
432 return i
437 --- View next tag. This is the same as tag.viewidx(1).
438 -- @param screen The screen number.
439 function tag.viewnext(screen)
440 return tag.viewidx(1, screen)
443 --- View previous tag. This is the same a tag.viewidx(-1).
444 -- @param screen The screen number.
445 function tag.viewprev(screen)
446 return tag.viewidx(-1, screen)
449 --- View only a tag.
450 -- @param t The tag object.
451 function tag.viewonly(t)
452 local tags = tag.gettags(tag.getscreen(t))
453 -- First, untag everyone except the viewed tag.
454 for _, _tag in pairs(tags) do
455 if _tag ~= t then
456 _tag.selected = false
459 -- Then, set this one to selected.
460 -- We need to do that in 2 operations so we avoid flickering and several tag
461 -- selected at the same time.
462 t.selected = true
463 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
466 --- View only a set of tags.
467 -- @param tags A table with tags to view only.
468 -- @param screen Optional screen number of the tags.
469 function tag.viewmore(tags, screen)
470 local screen = screen or capi.mouse.screen
471 local screen_tags = tag.gettags(screen)
472 for _, _tag in ipairs(screen_tags) do
473 if not util.table.hasitem(tags, _tag) then
474 _tag.selected = false
477 for _, _tag in ipairs(tags) do
478 _tag.selected = true
480 capi.screen[screen]:emit_signal("tag::history::update")
483 --- Toggle selection of a tag
484 -- @param tag Tag to be toggled
485 function tag.viewtoggle(t)
486 t.selected = not t.selected
487 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
490 --- Get tag data table.
491 -- @param tag The Tag.
492 -- @return The data table.
493 function tag.getdata(_tag)
494 return data.tags[_tag]
497 --- Get a tag property.
498 -- @param _tag The tag.
499 -- @param prop The property name.
500 -- @return The property.
501 function tag.getproperty(_tag, prop)
502 if data.tags[_tag] then
503 return data.tags[_tag][prop]
507 --- Set a tag property.
508 -- This properties are internal to awful. Some are used to draw taglist, or to
509 -- handle layout, etc.
510 -- @param _tag The tag.
511 -- @param prop The property name.
512 -- @param value The value.
513 function tag.setproperty(_tag, prop, value)
514 if not data.tags[_tag] then
515 data.tags[_tag] = {}
517 data.tags[_tag][prop] = value
518 _tag:emit_signal("property::" .. prop)
521 --- Tag a client with the set of current tags.
522 -- @param c The client to tag.
523 function tag.withcurrent(c)
524 local tags = {}
525 for k, t in ipairs(c:tags()) do
526 if tag.getscreen(t) == c.screen then
527 table.insert(tags, t)
530 if #tags == 0 then
531 tags = tag.selectedlist(c.screen)
533 c:tags(tags)
536 local function attached_connect_signal_screen(screen, sig, func)
537 capi.tag.connect_signal(sig, function(_tag, ...)
538 if tag.getscreen(_tag) == screen then
539 func(_tag)
541 end)
544 --- Add a signal to all attached tag and all tag that will be attached in the
545 -- future. When a tag is detach from the screen, its signal is removed.
546 -- @param screen The screen concerned, or all if nil.
547 function tag.attached_connect_signal(screen, ...)
548 if screen then
549 attached_connect_signal_screen(screen, ...)
550 else
551 capi.tag.connect_signal(...)
555 -- Register standards signals
556 capi.client.connect_signal("manage", function(c, startup)
557 -- If we are not managing this application at startup,
558 -- move it to the screen where the mouse is.
559 -- We only do it for "normal" windows (i.e. no dock, etc).
560 if not startup and c.type ~= "desktop" and c.type ~= "dock" then
561 if c.transient_for then
562 c.screen = c.transient_for.screen
563 if not c.sticky then
564 c:tags(c.transient_for:tags())
566 else
567 c.screen = capi.mouse.screen
570 end)
572 capi.client.connect_signal("manage", tag.withcurrent)
573 capi.client.connect_signal("property::screen", tag.withcurrent)
574 capi.tag.connect_signal("request::select", tag.viewonly)
576 capi.tag.add_signal("property::hide")
577 capi.tag.add_signal("property::icon")
578 capi.tag.add_signal("property::layout")
579 capi.tag.add_signal("property::mwfact")
580 capi.tag.add_signal("property::ncol")
581 capi.tag.add_signal("property::nmaster")
582 capi.tag.add_signal("property::windowfact")
583 capi.tag.add_signal("property::screen")
584 capi.tag.add_signal("property::index")
586 for s = 1, capi.screen.count() do
587 capi.screen[s]:add_signal("tag::history::update")
588 capi.screen[s]:connect_signal("tag::history::update", tag.history.update)
591 function tag.mt:__call(...)
592 return tag.new(...)
595 return setmetatable(tag, tag.mt)
597 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80