Merge branch 'fix-ldoc-set-spacing' of git://github.com/actionless/awesome
[awesome.git] / lib / awful / tag.lua.in
blob834ef5110f116651109c5d641dc8863dc67c4f4a
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 local rm_index = nil
51 for i, t in ipairs(tmp_tags) do
52 if t == target_tag then
53 table.remove(tmp_tags, i)
54 rm_index = i
55 break
56 end
57 end
59 table.insert(tmp_tags, new_index, target_tag)
61 for i=new_index < rm_index and new_index or rm_index, #tmp_tags do
62 local tmp_tag = tmp_tags[i]
63 tag.setscreen(tmp_tag, scr)
64 tag.setproperty(tmp_tag, "index", i)
65 end
66 end
68 --- Swap 2 tags
69 -- @param tag1 The first tag
70 -- @param tag2 The second tag
71 function tag.swap(tag1,tag2)
72 local idx1, idx2 = tag.getidx(tag1),tag.getidx(tag2)
73 local src2, src1 = tag.getscreen(tag2),tag.getscreen(tag1)
74 tag.setscreen(tag2,src1)
75 tag.move(idx1,tag2)
76 tag.setscreen(tag1,scr2)
77 tag.move(idx2,tag1)
78 end
80 --- Add a tag.
81 -- @param name The tag name, a string
82 -- @param props The tags properties, a table
83 -- @return The created tag
84 function tag.add(name, props)
85 local properties = props or {}
87 -- Be sure to set the screen before the tag is activated to avoid function
88 -- connected to property::activated to be called without a valid tag.
89 -- set properies cannot be used as this has to be set before the first signal
90 -- is sent
91 properties.screen = properties.screen or capi.mouse.screen
93 -- Index is also required
94 properties.index = (#tag.gettags(properties.screen))+1
96 local newtag = capi.tag{ name = name }
98 -- Start with a fresh property table to avoid collisions with unsupported data
99 data.tags[newtag] = {screen=properties.screen, index=properties.index}
101 newtag.activated = true
103 for k, v in pairs(properties) do
104 tag.setproperty(newtag, k, v)
107 return newtag
110 --- Create a set of tags and attach it to a screen.
111 -- @param names The tag name, in a table
112 -- @param screen The tag screen, or 1 if not set.
113 -- @param layout The layout or layout table to set for this tags by default.
114 -- @return A table with all created tags.
115 function tag.new(names, screen, layout)
116 local screen = screen or 1
117 local tags = {}
118 for id, name in ipairs(names) do
119 table.insert(tags, id, tag.add(name, {screen = screen,
120 layout = (layout and layout[id]) or
121 layout}))
122 -- Select the first tag.
123 if id == 1 then
124 tags[id].selected = true
128 return tags
131 --- Find a suitable fallback tag.
132 -- @param screen The screen number to look for a tag on. [mouse.screen]
133 -- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)]
134 function tag.find_fallback(screen, invalids)
135 local scr = screen or capi.mouse.screen
136 local t = invalids or tag.selectedlist(scr)
138 for _, v in pairs(tag.gettags(scr)) do
139 if not util.table.hasitem(t, v) then return v end
143 --- Delete a tag.
144 -- @param target_tag Optional tag object to delete. [selected()]
145 -- @param fallback_tag Tag to assign stickied tags to. [~selected()]
146 -- @return Returns true if the tag is successfully deleted, nil otherwise.
147 -- If there are no clients exclusively on this tag then delete it. Any
148 -- stickied clients are assigned to the optional 'fallback_tag'.
149 -- If after deleting the tag there is no selected tag, try and restore from
150 -- history or select the first tag on the screen.
151 function tag.delete(target_tag, fallback_tag)
152 -- abort if no tag is passed or currently selected
153 local target_tag = target_tag or tag.selected()
154 if target_tag == nil or target_tag.activated == false then return end
156 local target_scr = tag.getscreen(target_tag)
157 local tags = tag.gettags(target_scr)
158 local idx = tag.getidx(target_tag)
159 local ntags = #tags
161 -- We can't use the target tag as a fallback.
162 local fallback_tag = fallback_tag
163 if fallback_tag == target_tag then return end
165 -- No fallback_tag provided, try and get one.
166 if fallback_tag == nil then
167 fallback_tag = tag.find_fallback(target_scr, {target_tag})
170 -- Abort if we would have un-tagged clients.
171 local clients = target_tag:clients()
172 if ( #clients > 0 and ntags <= 1 ) or fallback_tag == nil then return end
174 -- Move the clients we can off of this tag.
175 for _, c in pairs(clients) do
177 -- If a client has only this tag, or stickied clients with
178 -- nowhere to go, abort.
179 if (not c.sticky and #c:tags() == 1) or
180 (c.sticky and fallback_tag == nil) then
181 return
182 -- If a client has multiple tags, then do not move it to fallback
183 elseif #c:tags() < 2 then
184 c:tags({fallback_tag})
188 -- delete the tag
189 data.tags[target_tag].screen = nil
190 target_tag.activated = false
192 -- Update all indexes
193 for i=idx+1,#tags do
194 tag.setproperty(tags[i], "index", i-1)
197 -- If no tags are visible, try and view one.
198 if tag.selected(target_scr) == nil and ntags > 0 then
199 tag.history.restore(nil, 1)
200 if tag.selected(target_scr) == nil then
201 tags[tags[1] == target_tag and 2 or 1].selected = true
205 return true
208 --- Update the tag history.
209 -- @param obj Screen object.
210 function tag.history.update(obj)
211 local s = obj.index
212 local curtags = tag.selectedlist(s)
213 -- create history table
214 if not data.history[s] then
215 data.history[s] = {}
216 else
217 if data.history[s].current then
218 -- Check that the list is not identical
219 local identical = #data.history[s].current == #curtags
220 if identical then
221 for idx, _tag in ipairs(data.history[s].current) do
222 if curtags[idx] ~= _tag then
223 identical = false
224 break
229 -- Do not update history the table are identical
230 if identical then return end
233 -- Limit history
234 if #data.history[s] >= tag.history.limit then
235 for i = tag.history.limit, #data.history[s] do
236 data.history[s][i] = nil
241 -- store previously selected tags in the history table
242 table.insert(data.history[s], 1, data.history[s].current)
243 data.history[s].previous = data.history[s][1]
244 -- store currently selected tags
245 data.history[s].current = setmetatable(curtags, { __mode = 'v' })
248 --- Revert tag history.
249 -- @param screen The screen number.
250 -- @param idx Index in history. Defaults to "previous" which is a special index
251 -- toggling between last two selected sets of tags. Number (eg 1) will go back
252 -- to the given index in history.
253 function tag.history.restore(screen, idx)
254 local s = screen or capi.mouse.screen
255 local i = idx or "previous"
256 local sel = tag.selectedlist(s)
257 -- do nothing if history empty
258 if not data.history[s] or not data.history[s][i] then return end
259 -- if all tags been deleted, try next entry
260 if #data.history[s][i] == 0 then
261 if i == "previous" then i = 0 end
262 tag.history.restore(s, i + 1)
263 return
265 -- deselect all tags
266 tag.viewnone(s)
267 -- select tags from the history entry
268 for _, t in ipairs(data.history[s][i]) do
269 t.selected = true
271 -- update currently selected tags table
272 data.history[s].current = data.history[s][i]
273 -- store previously selected tags
274 data.history[s].previous = setmetatable(sel, { __mode = 'v' })
275 -- remove the reverted history entry
276 if i ~= "previous" then table.remove(data.history[s], i) end
279 --- Get a list of all tags on a screen
280 -- @param s Screen number
281 -- @return A table with all available tags
282 function tag.gettags(s)
283 local tags = {}
284 for i, t in ipairs(root.tags()) do
285 if tag.getscreen(t) == s then
286 table.insert(tags, t)
290 table.sort(tags, function(a, b)
291 return (tag.getproperty(a, "index") or 9999) < (tag.getproperty(b, "index") or 9999)
292 end)
293 return tags
296 --- Set a tag's screen
297 -- @param t tag object
298 -- @param s Screen number
299 function tag.setscreen(t, s)
300 local s = s or capi.mouse.screen
301 local sel = tag.selected
302 local old_screen = tag.getproperty(t, "screen")
303 if s == old_screen then return end
305 -- Keeping the old index make very little sense when changing screen
306 tag.setproperty(t, "index", nil)
308 -- Change the screen
309 tag.setproperty(t, "screen", s)
311 -- Make sure the client's screen matches its tags
312 for k,c in ipairs(t:clients()) do
313 c.screen = s --Move all clients
314 c:tags({t})
317 -- Update all indexes
318 for _,screen in ipairs {old_screen,s} do
319 for i,t in ipairs(tag.gettags(screen)) do
320 tag.setproperty(t, "index", i)
324 -- Restore the old screen history if the tag was selected
325 if sel then
326 tag.history.restore(old_screen,1)
330 --- Get a tag's screen
331 -- @param t tag object
332 -- @return Screen number
333 function tag.getscreen(t)
334 return tag.getproperty(t, "screen")
337 --- Return a table with all visible tags
338 -- @param s Screen number.
339 -- @return A table with all selected tags.
340 function tag.selectedlist(s)
341 local screen = s or capi.mouse.screen
342 local tags = tag.gettags(screen)
343 local vtags = {}
344 for i, t in pairs(tags) do
345 if t.selected then
346 vtags[#vtags + 1] = t
349 return vtags
352 --- Return only the first visible tag.
353 -- @param s Screen number.
354 function tag.selected(s)
355 return tag.selectedlist(s)[1]
358 --- Set master width factor.
359 -- @param mwfact Master width factor.
360 -- @param t The tag to modify, if null tag.selected() is used.
361 function tag.setmwfact(mwfact, t)
362 local t = t or tag.selected()
363 if mwfact >= 0 and mwfact <= 1 then
364 tag.setproperty(t, "mwfact", mwfact)
368 --- Increase master width factor.
369 -- @param add Value to add to master width factor.
370 -- @param t The tag to modify, if null tag.selected() is used.
371 function tag.incmwfact(add, t)
372 tag.setmwfact(tag.getmwfact(t) + add, t)
375 --- Get master width factor.
376 -- @param t Optional tag.
377 function tag.getmwfact(t)
378 local t = t or tag.selected()
379 return tag.getproperty(t, "mwfact") or 0.5
382 --- Set the number of master windows.
383 -- @param nmaster The number of master windows.
384 -- @param t Optional tag.
385 function tag.setnmaster(nmaster, t)
386 local t = t or tag.selected()
387 if nmaster >= 0 then
388 tag.setproperty(t, "nmaster", nmaster)
392 --- Get the number of master windows.
393 -- @param t Optional tag.
394 function tag.getnmaster(t)
395 local t = t or tag.selected()
396 return tag.getproperty(t, "nmaster") or 1
399 --- Increase the number of master windows.
400 -- @param add Value to add to number of master windows.
401 -- @param t The tag to modify, if null tag.selected() is used.
402 function tag.incnmaster(add, t)
403 tag.setnmaster(tag.getnmaster(t) + add, t)
407 --- Set the tag icon
408 -- @param icon the icon to set, either path or image object
409 -- @param _tag the tag
410 function tag.seticon(icon, _tag)
411 local _tag = _tag or tag.selected()
412 tag.setproperty(_tag, "icon", icon)
415 --- Get the tag icon
416 -- @param _tag the tag
417 function tag.geticon(_tag)
418 local _tag = _tag or tag.selected()
419 return tag.getproperty(_tag, "icon")
422 --- Set number of column windows.
423 -- @param ncol The number of column.
424 -- @param t The tag to modify, if null tag.selected() is used.
425 function tag.setncol(ncol, t)
426 local t = t or tag.selected()
427 if ncol >= 1 then
428 tag.setproperty(t, "ncol", ncol)
432 --- Get number of column windows.
433 -- @param t Optional tag.
434 function tag.getncol(t)
435 local t = t or tag.selected()
436 return tag.getproperty(t, "ncol") or 1
439 --- Increase number of column windows.
440 -- @param add Value to add to number of column windows.
441 -- @param t The tag to modify, if null tag.selected() is used.
442 function tag.incncol(add, t)
443 tag.setncol(tag.getncol(t) + add, t)
446 --- View no tag.
447 -- @param Optional screen number.
448 function tag.viewnone(screen)
449 local tags = tag.gettags(screen or capi.mouse.screen)
450 for i, t in pairs(tags) do
451 t.selected = false
455 --- View a tag by its taglist index.
456 -- @param i The relative index to see.
457 -- @param screen Optional screen number.
458 function tag.viewidx(i, screen)
459 local screen = screen or capi.mouse.screen
460 local tags = tag.gettags(screen)
461 local showntags = {}
462 for k, t in ipairs(tags) do
463 if not tag.getproperty(t, "hide") then
464 table.insert(showntags, t)
467 local sel = tag.selected(screen)
468 tag.viewnone(screen)
469 for k, t in ipairs(showntags) do
470 if t == sel then
471 showntags[util.cycle(#showntags, k + i)].selected = true
474 capi.screen[screen]:emit_signal("tag::history::update")
477 --- Get a tag's index in the gettags() table.
478 -- @param query_tag The tag object to find. [selected()]
479 -- @return The index of the tag, nil if the tag is not found.
480 function tag.getidx(query_tag)
481 local query_tag = query_tag or tag.selected()
482 if query_tag == nil then return end
484 for i, t in ipairs(tag.gettags(tag.getscreen(query_tag))) do
485 if t == query_tag then
486 return i
491 --- View next tag. This is the same as tag.viewidx(1).
492 -- @param screen The screen number.
493 function tag.viewnext(screen)
494 return tag.viewidx(1, screen)
497 --- View previous tag. This is the same a tag.viewidx(-1).
498 -- @param screen The screen number.
499 function tag.viewprev(screen)
500 return tag.viewidx(-1, screen)
503 --- View only a tag.
504 -- @param t The tag object.
505 function tag.viewonly(t)
506 local tags = tag.gettags(tag.getscreen(t))
507 -- First, untag everyone except the viewed tag.
508 for _, _tag in pairs(tags) do
509 if _tag ~= t then
510 _tag.selected = false
513 -- Then, set this one to selected.
514 -- We need to do that in 2 operations so we avoid flickering and several tag
515 -- selected at the same time.
516 t.selected = true
517 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
520 --- View only a set of tags.
521 -- @param tags A table with tags to view only.
522 -- @param screen Optional screen number of the tags.
523 function tag.viewmore(tags, screen)
524 local screen = screen or capi.mouse.screen
525 local screen_tags = tag.gettags(screen)
526 for _, _tag in ipairs(screen_tags) do
527 if not util.table.hasitem(tags, _tag) then
528 _tag.selected = false
531 for _, _tag in ipairs(tags) do
532 _tag.selected = true
534 capi.screen[screen]:emit_signal("tag::history::update")
537 --- Toggle selection of a tag
538 -- @param tag Tag to be toggled
539 function tag.viewtoggle(t)
540 t.selected = not t.selected
541 capi.screen[tag.getscreen(t)]:emit_signal("tag::history::update")
544 --- Get tag data table.
545 -- @param tag The Tag.
546 -- @return The data table.
547 function tag.getdata(_tag)
548 return data.tags[_tag]
551 --- Get a tag property.
552 -- @param _tag The tag.
553 -- @param prop The property name.
554 -- @return The property.
555 function tag.getproperty(_tag, prop)
556 if data.tags[_tag] then
557 return data.tags[_tag][prop]
561 --- Set a tag property.
562 -- This properties are internal to awful. Some are used to draw taglist, or to
563 -- handle layout, etc.
564 -- @param _tag The tag.
565 -- @param prop The property name.
566 -- @param value The value.
567 function tag.setproperty(_tag, prop, value)
568 if not data.tags[_tag] then
569 data.tags[_tag] = {}
571 data.tags[_tag][prop] = value
572 _tag:emit_signal("property::" .. prop)
575 --- Tag a client with the set of current tags.
576 -- @param c The client to tag.
577 function tag.withcurrent(c)
578 local tags = {}
579 for k, t in ipairs(c:tags()) do
580 if tag.getscreen(t) == c.screen then
581 table.insert(tags, t)
584 if #tags == 0 then
585 tags = tag.selectedlist(c.screen)
587 if #tags == 0 then
588 tags = tag.gettags(c.screen)
590 if #tags ~= 0 then
591 c:tags(tags)
595 local function attached_connect_signal_screen(screen, sig, func)
596 capi.tag.connect_signal(sig, function(_tag, ...)
597 if tag.getscreen(_tag) == screen then
598 func(_tag)
600 end)
603 --- Add a signal to all attached tag and all tag that will be attached in the
604 -- future. When a tag is detach from the screen, its signal is removed.
605 -- @param screen The screen concerned, or all if nil.
606 function tag.attached_connect_signal(screen, ...)
607 if screen then
608 attached_connect_signal_screen(screen, ...)
609 else
610 capi.tag.connect_signal(...)
614 -- Register standard signals.
615 capi.client.connect_signal("manage", function(c)
616 -- If we are not managing this application at startup,
617 -- move it to the screen where the mouse is.
618 -- We only do it for "normal" windows (i.e. no dock, etc).
619 if not awesome.startup and c.type ~= "desktop" and c.type ~= "dock" then
620 if c.transient_for then
621 c.screen = c.transient_for.screen
622 if not c.sticky then
623 c:tags(c.transient_for:tags())
625 else
626 c.screen = capi.mouse.screen
629 c:connect_signal("property::screen", tag.withcurrent)
630 end)
632 capi.client.connect_signal("manage", tag.withcurrent)
633 capi.tag.connect_signal("request::select", tag.viewonly)
635 capi.tag.add_signal("property::hide")
636 capi.tag.add_signal("property::icon")
637 capi.tag.add_signal("property::layout")
638 capi.tag.add_signal("property::mwfact")
639 capi.tag.add_signal("property::ncol")
640 capi.tag.add_signal("property::nmaster")
641 capi.tag.add_signal("property::windowfact")
642 capi.tag.add_signal("property::screen")
643 capi.tag.add_signal("property::index")
645 capi.screen.add_signal("tag::history::update")
646 for s = 1, capi.screen.count() do
647 capi.screen[s]:connect_signal("tag::history::update", tag.history.update)
650 function tag.mt:__call(...)
651 return tag.new(...)
654 return setmetatable(tag, tag.mt)
656 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80