diff options
Diffstat (limited to 'awesome/lib/awful/tag.lua')
-rw-r--r-- | awesome/lib/awful/tag.lua | 1505 |
1 files changed, 1505 insertions, 0 deletions
diff --git a/awesome/lib/awful/tag.lua b/awesome/lib/awful/tag.lua new file mode 100644 index 0000000..dbf3a60 --- /dev/null +++ b/awesome/lib/awful/tag.lua @@ -0,0 +1,1505 @@ +--------------------------------------------------------------------------- +--- Useful functions for tag manipulation. +-- +-- @author Julien Danjou <julien@danjou.info> +-- @copyright 2008 Julien Danjou +-- @module tag +--------------------------------------------------------------------------- + +-- Grab environment we need +local util = require("awful.util") +local ascreen = require("awful.screen") +local beautiful = require("beautiful") +local object = require("gears.object") +local timer = require("gears.timer") +local pairs = pairs +local ipairs = ipairs +local table = table +local setmetatable = setmetatable +local capi = +{ + tag = tag, + screen = screen, + mouse = mouse, + client = client, + root = root +} + +local function get_screen(s) + return s and capi.screen[s] +end + +local tag = {object = {}, mt = {} } + +-- Private data +local data = {} +data.history = {} + +-- History functions +tag.history = {} +tag.history.limit = 20 + +-- Default values +local defaults = {} + +-- The gap between clients (in points). +defaults.gap = 0 + +-- The default gap_count. +defaults.gap_single_client = true + +-- The default master fill policy. +defaults.master_fill_policy = "expand" + +-- The default master width factor. +defaults.master_width_factor = 0.5 + +-- The default master count. +defaults.master_count = 1 + +-- The default column count. +defaults.column_count = 1 + +-- screen.tags depend on index, it cannot be used by awful.tag +local function raw_tags(scr) + local tmp_tags = {} + for _, t in ipairs(root.tags()) do + if get_screen(t.screen) == scr then + table.insert(tmp_tags, t) + end + end + + return tmp_tags +end + +--- The number of elements kept in the history. +-- @tfield integer awful.tag.history.limit +-- @tparam[opt=20] integer limit + +--- The tag index. +-- +-- The index is the position as shown in the `awful.widget.taglist`. +-- +-- **Signal:** +-- +-- * *property::index* +-- +-- @property index +-- @param integer +-- @treturn number The tag index. + +function tag.object.set_index(self, idx) + local scr = get_screen(tag.getproperty(self, "screen")) + + -- screen.tags cannot be used as it depend on index + local tmp_tags = raw_tags(scr) + + -- sort the tags by index + table.sort(tmp_tags, function(a, b) + local ia, ib = tag.getproperty(a, "index"), tag.getproperty(b, "index") + return (ia or math.huge) < (ib or math.huge) + end) + + if (not idx) or (idx < 1) or (idx > #tmp_tags) then + return + end + + local rm_index = nil + + for i, t in ipairs(tmp_tags) do + if t == self then + table.remove(tmp_tags, i) + rm_index = i + break + end + end + + table.insert(tmp_tags, idx, self) + for i = idx < rm_index and idx or rm_index, #tmp_tags do + local tmp_tag = tmp_tags[i] + tag.object.set_screen(tmp_tag, scr) + tag.setproperty(tmp_tag, "index", i) + end +end + +function tag.object.get_index(query_tag) + + local idx = tag.getproperty(query_tag, "index") + + if idx then return idx end + + -- Get an unordered list of tags + local tags = raw_tags(query_tag.screen) + + -- Too bad, lets compute it + for i, t in ipairs(tags) do + if t == query_tag then + tag.setproperty(t, "index", i) + return i + end + end +end + +--- Move a tag to an absolute position in the screen[]:tags() table. +-- @deprecated awful.tag.move +-- @param new_index Integer absolute position in the table to insert. +-- @param target_tag The tag that should be moved. If null, the currently +-- selected tag is used. +-- @see index +function tag.move(new_index, target_tag) + util.deprecate("Use t.index = new_index instead of awful.tag.move") + + target_tag = target_tag or ascreen.focused().selected_tag + tag.object.set_index(target_tag, new_index) +end + +--- Swap 2 tags +-- @function tag.swap +-- @param tag2 The second tag +-- @see client.swap +function tag.object.swap(self, tag2) + local idx1, idx2 = tag.object.get_index(self), tag.object.get_index(tag2) + local scr2, scr1 = tag.getproperty(tag2, "screen"), tag.getproperty(self, "screen") + + -- If they are on the same screen, avoid recomputing the whole table + -- for nothing. + if scr1 == scr2 then + tag.setproperty(self, "index", idx2) + tag.setproperty(tag2, "index", idx1) + else + tag.object.set_screen(tag2, scr1) + tag.object.set_index (tag2, idx1) + tag.object.set_screen(self, scr2) + tag.object.set_index (self, idx2) + end +end + +--- Swap 2 tags +-- @deprecated awful.tag.swap +-- @see tag.swap +-- @param tag1 The first tag +-- @param tag2 The second tag +function tag.swap(tag1, tag2) + util.deprecate("Use t:swap(tag2) instead of awful.tag.swap") + + tag.object.swap(tag1, tag2) +end + +--- Add a tag. +-- +-- This function allow to create tags from a set of properties: +-- +-- local t = awful.tag.add("my new tag", { +-- screen = screen.primary, +-- layout = awful.layout.suit.max, +-- }) +-- +-- @function awful.tag.add +-- @param name The tag name, a string +-- @param props The tags inital properties, a table +-- @return The created tag +-- @see tag.delete +function tag.add(name, props) + local properties = props or {} + + -- Be sure to set the screen before the tag is activated to avoid function + -- connected to property::activated to be called without a valid tag. + -- set properties cannot be used as this has to be set before the first + -- signal is sent + properties.screen = get_screen(properties.screen or ascreen.focused()) + -- Index is also required + properties.index = properties.index or #raw_tags(properties.screen)+1 + + local newtag = capi.tag{ name = name } + + -- Start with a fresh property table to avoid collisions with unsupported data + newtag.data.awful_tag_properties = {screen=properties.screen, index=properties.index} + + newtag.activated = true + + for k, v in pairs(properties) do + -- `rawget` doesn't work on userdata, `:clients()` is the only relevant + -- entry. + if k == "clients" or tag.object[k] then + newtag[k](newtag, v) + else + newtag[k] = v + end + end + + return newtag +end + +--- Create a set of tags and attach it to a screen. +-- @function awful.tag.new +-- @param names The tag name, in a table +-- @param screen The tag screen, or 1 if not set. +-- @param layout The layout or layout table to set for this tags by default. +-- @return A table with all created tags. +function tag.new(names, screen, layout) + screen = get_screen(screen or 1) + local tags = {} + for id, name in ipairs(names) do + table.insert(tags, id, tag.add(name, {screen = screen, + layout = (layout and layout[id]) or + layout})) + -- Select the first tag. + if id == 1 then + tags[id].selected = true + end + end + + return tags +end + +--- Find a suitable fallback tag. +-- @function awful.tag.find_fallback +-- @param screen The screen to look for a tag on. [awful.screen.focused()] +-- @param invalids A table of tags we consider unacceptable. [selectedlist(scr)] +function tag.find_fallback(screen, invalids) + local scr = screen or ascreen.focused() + local t = invalids or scr.selected_tags + + for _, v in pairs(scr.tags) do + if not util.table.hasitem(t, v) then return v end + end +end + +--- Delete a tag. +-- +-- To delete the current tag: +-- +-- mouse.screen.selected_tag:delete() +-- +-- @function tag.delete +-- @see awful.tag.add +-- @see awful.tag.find_fallback +-- @tparam[opt=awful.tag.find_fallback()] tag fallback_tag Tag to assign +-- stickied tags to. +-- @tparam[opt=false] boolean force Move even non-sticky clients to the fallback +-- tag. +-- @return Returns true if the tag is successfully deleted. +-- If there are no clients exclusively on this tag then delete it. Any +-- stickied clients are assigned to the optional 'fallback_tag'. +-- If after deleting the tag there is no selected tag, try and restore from +-- history or select the first tag on the screen. +function tag.object.delete(self, fallback_tag, force) + -- abort if the taf isn't currently activated + if not self.activated then return false end + + local target_scr = get_screen(tag.getproperty(self, "screen")) + local tags = target_scr.tags + local idx = tag.object.get_index(self) + local ntags = #tags + + -- We can't use the target tag as a fallback. + if fallback_tag == self then return false end + + -- No fallback_tag provided, try and get one. + if fallback_tag == nil then + fallback_tag = tag.find_fallback(target_scr, {self}) + end + + -- Abort if we would have un-tagged clients. + local clients = self:clients() + if #clients > 0 and fallback_tag == nil then return false end + + -- Move the clients we can off of this tag. + for _, c in pairs(clients) do + local nb_tags = #c:tags() + + -- If a client has only this tag, or stickied clients with + -- nowhere to go, abort. + if (not c.sticky and nb_tags == 1 and not force) then + return + -- If a client has multiple tags, then do not move it to fallback + elseif nb_tags < 2 then + c:tags({fallback_tag}) + end + end + + -- delete the tag + self.data.awful_tag_properties.screen = nil + self.activated = false + + -- Update all indexes + for i=idx+1, #tags do + tag.setproperty(tags[i], "index", i-1) + end + + -- If no tags are visible (and we did not delete the lasttag), try and + -- view one. The > 1 is because ntags is no longer synchronized with the + -- current count. + if target_scr.selected_tag == nil and ntags > 1 then + tag.history.restore(target_scr, 1) + if target_scr.selected_tag == nil then + local other_tag = tags[tags[1] == self and 2 or 1] + if other_tag then + other_tag.selected = true + end + end + end + + return true +end + +--- Delete a tag. +-- @deprecated awful.tag.delete +-- @see tag.delete +-- @param target_tag Optional tag object to delete. [selected()] +-- @param fallback_tag Tag to assign stickied tags to. [~selected()] +-- @return Returns true if the tag is successfully deleted, nil otherwise. +-- If there are no clients exclusively on this tag then delete it. Any +-- stickied clients are assigned to the optional 'fallback_tag'. +-- If after deleting the tag there is no selected tag, try and restore from +-- history or select the first tag on the screen. +function tag.delete(target_tag, fallback_tag) + util.deprecate("Use t:delete(fallback_tag) instead of awful.tag.delete") + + return tag.object.delete(target_tag, fallback_tag) +end + +--- Update the tag history. +-- @function awful.tag.history.update +-- @param obj Screen object. +function tag.history.update(obj) + local s = get_screen(obj) + local curtags = s.selected_tags + -- create history table + if not data.history[s] then + data.history[s] = {} + else + if data.history[s].current then + -- Check that the list is not identical + local identical = #data.history[s].current == #curtags + if identical then + for idx, _tag in ipairs(data.history[s].current) do + if curtags[idx] ~= _tag then + identical = false + break + end + end + end + + -- Do not update history the table are identical + if identical then return end + end + + -- Limit history + if #data.history[s] >= tag.history.limit then + for i = tag.history.limit, #data.history[s] do + data.history[s][i] = nil + end + end + end + + -- store previously selected tags in the history table + table.insert(data.history[s], 1, data.history[s].current) + data.history[s].previous = data.history[s][1] + -- store currently selected tags + data.history[s].current = setmetatable(curtags, { __mode = 'v' }) +end + +--- Revert tag history. +-- @function awful.tag.history.restore +-- @param screen The screen. +-- @param idx Index in history. Defaults to "previous" which is a special index +-- toggling between last two selected sets of tags. Number (eg 1) will go back +-- to the given index in history. +function tag.history.restore(screen, idx) + local s = get_screen(screen or ascreen.focused()) + local i = idx or "previous" + local sel = s.selected_tags + -- do nothing if history empty + if not data.history[s] or not data.history[s][i] then return end + -- if all tags been deleted, try next entry + if #data.history[s][i] == 0 then + if i == "previous" then i = 0 end + tag.history.restore(s, i + 1) + return + end + -- deselect all tags + tag.viewnone(s) + -- select tags from the history entry + for _, t in ipairs(data.history[s][i]) do + if t.activated and t.screen then + t.selected = true + end + end + -- update currently selected tags table + data.history[s].current = data.history[s][i] + -- store previously selected tags + data.history[s].previous = setmetatable(sel, { __mode = 'v' }) + -- remove the reverted history entry + if i ~= "previous" then table.remove(data.history[s], i) end + + s:emit_signal("tag::history::update") +end + +--- Get a list of all tags on a screen +-- @deprecated awful.tag.gettags +-- @tparam screen s Screen +-- @return A table with all available tags +-- @see screen.tags +function tag.gettags(s) + util.deprecate("Use s.tags instead of awful.tag.gettags") + + s = get_screen(s) + + return s and s.tags or {} +end + +--- Find a tag by name +-- @tparam[opt] screen s The screen of the tag +-- @tparam string name The name of the tag +-- @return The tag found, or `nil` +function tag.find_by_name(s, name) + local tags = s and s.tags or root.tags() + for _, t in ipairs(tags) do + if name == t.name then + return t + end + end +end + +--- The tag screen. +-- +-- **Signal:** +-- +-- * *property::screen* +-- +-- @property screen +-- @param screen +-- @see screen + +function tag.object.set_screen(t, s) + + s = get_screen(s or ascreen.focused()) + local sel = tag.selected + local old_screen = get_screen(tag.getproperty(t, "screen")) + + if s == old_screen then return end + + -- Keeping the old index make very little sense when changing screen + tag.setproperty(t, "index", nil) + + -- Change the screen + tag.setproperty(t, "screen", s) + tag.setproperty(t, "index", #s:get_tags(true)) + + -- Make sure the client's screen matches its tags + for _,c in ipairs(t:clients()) do + c.screen = s --Move all clients + c:tags({t}) + end + + -- Update all indexes + for i,t2 in ipairs(old_screen.tags) do + tag.setproperty(t2, "index", i) + end + + -- Restore the old screen history if the tag was selected + if sel then + tag.history.restore(old_screen, 1) + end +end + +--- Set a tag's screen +-- @deprecated awful.tag.setscreen +-- @see screen +-- @param s Screen +-- @param t tag object +function tag.setscreen(s, t) + -- For API consistency, the arguments have been swapped for Awesome 3.6 + -- this method is already deprecated, so be silent and swap the args + if type(t) == "number" then + s, t = t, s + end + + util.deprecate("Use t.screen = s instead of awful.tag.setscreen(t, s)") + + tag.object.set_screen(t, s) +end + +--- Get a tag's screen +-- @deprecated awful.tag.getscreen +-- @see screen +-- @param[opt] t tag object +-- @return Screen number +function tag.getscreen(t) + util.deprecate("Use t.screen instead of awful.tag.getscreen(t)") + + -- A new getter is not required + + t = t or ascreen.focused().selected_tag + local prop = tag.getproperty(t, "screen") + return prop and prop.index +end + +--- Return a table with all visible tags +-- @deprecated awful.tag.selectedlist +-- @param s Screen. +-- @return A table with all selected tags. +-- @see screen.selected_tags +function tag.selectedlist(s) + util.deprecate("Use s.selected_tags instead of awful.tag.selectedlist") + + s = get_screen(s or ascreen.focused()) + + return s.selected_tags +end + +--- Return only the first visible tag. +-- @deprecated awful.tag.selected +-- @param s Screen. +-- @see screen.selected_tag +function tag.selected(s) + util.deprecate("Use s.selected_tag instead of awful.tag.selected") + + s = get_screen(s or ascreen.focused()) + + return s.selected_tag +end + +--- The default master width factor +-- +-- @beautiful beautiful.master_width_factor +-- @param number (default: 0.5) +-- @see master_width_factor +-- @see gap + +--- The tag master width factor. +-- +-- The master width factor is one of the 5 main properties used to configure +-- the `layout`. Each layout interpret (or ignore) this property differenly. +-- +-- See the layout suit documentation for information about how the master width +-- factor is used. +-- +-- **Signal:** +-- +-- * *property::mwfact* (deprecated) +-- * *property::master_width_factor* +-- +-- @property master_width_factor +-- @param number Between 0 and 1 +-- @see master_count +-- @see column_count +-- @see master_fill_policy +-- @see gap + +function tag.object.set_master_width_factor(t, mwfact) + if mwfact >= 0 and mwfact <= 1 then + tag.setproperty(t, "mwfact", mwfact) + tag.setproperty(t, "master_width_factor", mwfact) + end +end + +function tag.object.get_master_width_factor(t) + return tag.getproperty(t, "master_width_factor") + or beautiful.master_width_factor + or defaults.master_width_factor +end + +--- Set master width factor. +-- @deprecated awful.tag.setmwfact +-- @see master_fill_policy +-- @see master_width_factor +-- @param mwfact Master width factor. +-- @param t The tag to modify, if null tag.selected() is used. +function tag.setmwfact(mwfact, t) + util.deprecate("Use t.master_width_factor = mwfact instead of awful.tag.setmwfact") + + tag.object.get_master_width_factor(t or ascreen.focused().selected_tag, mwfact) +end + +--- Increase master width factor. +-- @function awful.tag.incmwfact +-- @see master_width_factor +-- @param add Value to add to master width factor. +-- @param t The tag to modify, if null tag.selected() is used. +function tag.incmwfact(add, t) + t = t or t or ascreen.focused().selected_tag + tag.object.set_master_width_factor(t, tag.object.get_master_width_factor(t) + add) +end + +--- Get master width factor. +-- @deprecated awful.tag.getmwfact +-- @see master_width_factor +-- @see master_fill_policy +-- @param[opt] t The tag. +function tag.getmwfact(t) + util.deprecate("Use t.master_width_factor instead of awful.tag.getmwfact") + + return tag.object.get_master_width_factor(t or ascreen.focused().selected_tag) +end + +--- An ordered list of layouts. +-- `awful.tag.layout` Is usually defined in `rc.lua`. It store the list of +-- layouts used when selecting the previous and next layouts. This is the +-- default: +-- +-- -- Table of layouts to cover with awful.layout.inc, order matters. +-- awful.layout.layouts = { +-- awful.layout.suit.floating, +-- awful.layout.suit.tile, +-- awful.layout.suit.tile.left, +-- awful.layout.suit.tile.bottom, +-- awful.layout.suit.tile.top, +-- awful.layout.suit.fair, +-- awful.layout.suit.fair.horizontal, +-- awful.layout.suit.spiral, +-- awful.layout.suit.spiral.dwindle, +-- awful.layout.suit.max, +-- awful.layout.suit.max.fullscreen, +-- awful.layout.suit.magnifier, +-- awful.layout.suit.corner.nw, +-- -- awful.layout.suit.corner.ne, +-- -- awful.layout.suit.corner.sw, +-- -- awful.layout.suit.corner.se, +-- } +-- +-- @field awful.tag.layouts + +--- The tag client layout. +-- +-- This property hold the layout. A layout can be either stateless or stateful. +-- Stateless layouts are used by default by Awesome. They tile clients without +-- any other overhead. They take an ordered list of clients and place them on +-- the screen. Stateful layouts create an object instance for each tags and +-- can store variables and metadata. Because of this, they are able to change +-- over time and be serialized (saved). +-- +-- Both types of layouts have valid usage scenarios. +-- +-- **Stateless layouts:** +-- +-- These layouts are stored in `awful.layout.suit`. They expose a table with 2 +-- fields: +-- +-- * **name** (*string*): The layout name. This should be unique. +-- * **arrange** (*function*): The function called when the clients need to be +-- placed. The only parameter is a table or arguments returned by +-- `awful.layout.parameters` +-- +-- **Stateful layouts:** +-- +-- The stateful layouts API is the same as stateless, but they are a function +-- returining a layout instead of a layout itself. They also should have an +-- `is_dynamic = true` property. If they don't, `awful.tag` will create a new +-- instance everytime the layout is set. If they do, the instance will be +-- cached and re-used. +-- +-- **Signal:** +-- +-- * *property::layout* +-- +-- @property layout +-- @see awful.tag.layouts +-- @tparam layout|function layout A layout table or a constructor function +-- @return The layout + +function tag.object.set_layout(t, layout) + -- Check if the signature match a stateful layout + if type(layout) == "function" or ( + type(layout) == "table" + and getmetatable(layout) + and getmetatable(layout).__call + ) then + if not t.dynamic_layout_cache then + t.dynamic_layout_cache = {} + end + + local instance = t.dynamic_layout_cache[layout] or layout(t) + + -- Always make sure the layout is notified it is enabled + if tag.getproperty(t, "screen").selected_tag == t and instance.wake_up then + instance:wake_up() + end + + -- Avoid creating the same layout twice, use layout:reset() to reset + if instance.is_dynamic then + t.dynamic_layout_cache[layout] = instance + end + + layout = instance + end + + tag.setproperty(t, "layout", layout) + + return layout +end + +--- Set layout. +-- @deprecated awful.tag.setlayout +-- @see layout +-- @param layout a layout table or a constructor function +-- @param t The tag to modify +-- @return The layout +function tag.setlayout(layout, t) + util.deprecate("Use t.layout = layout instead of awful.tag.setlayout") + + return tag.object.set_layout(t, layout) +end + +--- Define if the tag must be deleted when the last client is untagged. +-- +-- This is useful to create "throw-away" tags for operation like 50/50 +-- side-by-side views. +-- +-- local t = awful.tag.add("Temporary", { +-- screen = client.focus.screen, +-- volatile = true, +-- clients = { +-- client.focus, +-- awful.client.focus.history.get(client.focus.screen, 1) +-- } +-- } +-- +-- **Signal:** +-- +-- * *property::volatile* +-- +-- @property volatile +-- @param boolean + +-- Volatile accessors are implicit + +--- Set if the tag must be deleted when the last client is untagged +-- @deprecated awful.tag.setvolatile +-- @see volatile +-- @tparam boolean volatile If the tag must be deleted when the last client is untagged +-- @param t The tag to modify, if null tag.selected() is used. +function tag.setvolatile(volatile, t) + util.deprecate("Use t.volatile = volatile instead of awful.tag.setvolatile") + + tag.setproperty(t, "volatile", volatile) +end + +--- Get if the tag must be deleted when the last client closes +-- @deprecated awful.tag.getvolatile +-- @see volatile +-- @param t The tag to modify, if null tag.selected() is used. +-- @treturn boolean If the tag will be deleted when the last client is untagged +function tag.getvolatile(t) + util.deprecate("Use t.volatile instead of awful.tag.getvolatile") + + return tag.getproperty(t, "volatile") or false +end + +--- The default gap. +-- +-- @beautiful beautiful.useless_gap +-- @param number (default: 0) +-- @see gap +-- @see gap_single_client + +--- The gap (spacing, also called `useless_gap`) between clients. +-- +-- This property allow to waste space on the screen in the name of style, +-- unicorns and readability. +-- +-- **Signal:** +-- +-- * *property::useless_gap* +-- +-- @property gap +-- @param number The value has to be greater than zero. +-- @see gap_single_client + +function tag.object.set_gap(t, useless_gap) + if useless_gap >= 0 then + tag.setproperty(t, "useless_gap", useless_gap) + end +end + +function tag.object.get_gap(t) + return tag.getproperty(t, "useless_gap") + or beautiful.useless_gap + or defaults.gap +end + +--- Set the spacing between clients +-- @deprecated awful.tag.setgap +-- @see gap +-- @param useless_gap The spacing between clients +-- @param t The tag to modify, if null tag.selected() is used. +function tag.setgap(useless_gap, t) + util.deprecate("Use t.gap = useless_gap instead of awful.tag.setgap") + + tag.object.set_gap(t or ascreen.focused().selected_tag, useless_gap) +end + +--- Increase the spacing between clients +-- @function awful.tag.incgap +-- @see gap +-- @param add Value to add to the spacing between clients +-- @param t The tag to modify, if null tag.selected() is used. +function tag.incgap(add, t) + t = t or t or ascreen.focused().selected_tag + tag.object.set_gap(t, tag.object.get_gap(t) + add) +end + +--- Enable gaps for a single client. +-- +-- @beautiful beautiful.gap_single_client +-- @param boolean (default: true) +-- @see gap +-- @see gap_single_client + +--- Enable gaps for a single client. +-- +-- **Signal:** +-- +-- * *property::gap\_single\_client* +-- +-- @property gap_single_client +-- @param boolean Enable gaps for a single client + +function tag.object.set_gap_single_client(t, gap_single_client) + tag.setproperty(t, "gap_single_client", gap_single_client == true) +end + +function tag.object.get_gap_single_client(t) + local val = tag.getproperty(t, "gap_single_client") + if val ~= nil then + return val + end + val = beautiful.gap_single_client + if val ~= nil then + return val + end + return defaults.gap_single_client +end + +--- Get the spacing between clients. +-- @deprecated awful.tag.getgap +-- @see gap +-- @tparam[opt=tag.selected()] tag t The tag. +-- @tparam[opt] int numclients Number of (tiled) clients. Passing this will +-- return 0 for a single client. You can override this function to change +-- this behavior. +function tag.getgap(t, numclients) + util.deprecate("Use t.gap instead of awful.tag.getgap") + + if numclients == 1 then + return 0 + end + + return tag.object.get_gap(t or ascreen.focused().selected_tag) +end + +--- The default fill policy. +-- +-- ** Possible values**: +-- +-- * *expand*: Take all the space +-- * *master_width_factor*: Only take the ratio defined by the +-- `master_width_factor` +-- +-- @beautiful beautiful.master_fill_policy +-- @param string (default: "expand") +-- @see master_fill_policy + +--- Set size fill policy for the master client(s). +-- +-- ** Possible values**: +-- +-- * *expand*: Take all the space +-- * *master_width_factor*: Only take the ratio defined by the +-- `master_width_factor` +-- +-- **Signal:** +-- +-- * *property::master_fill_policy* +-- +-- @property master_fill_policy +-- @param string "expand" or "master_width_factor" + +function tag.object.get_master_fill_policy(t) + return tag.getproperty(t, "master_fill_policy") + or beautiful.master_fill_policy + or defaults.master_fill_policy +end + +--- Set size fill policy for the master client(s) +-- @deprecated awful.tag.setmfpol +-- @see master_fill_policy +-- @tparam string policy Can be set to +-- "expand" (fill all the available workarea) or +-- "master_width_factor" (fill only an area inside the master width factor) +-- @tparam[opt=tag.selected()] tag t The tag to modify +function tag.setmfpol(policy, t) + util.deprecate("Use t.master_fill_policy = policy instead of awful.tag.setmfpol") + + t = t or ascreen.focused().selected_tag + tag.setproperty(t, "master_fill_policy", policy) +end + +--- Toggle size fill policy for the master client(s) +-- between "expand" and "master_width_factor". +-- @function awful.tag.togglemfpol +-- @see master_fill_policy +-- @tparam tag t The tag to modify, if null tag.selected() is used. +function tag.togglemfpol(t) + t = t or ascreen.focused().selected_tag + + if tag.getmfpol(t) == "expand" then + tag.setproperty(t, "master_fill_policy", "master_width_factor") + else + tag.setproperty(t, "master_fill_policy", "expand") + end +end + +--- Get size fill policy for the master client(s) +-- @deprecated awful.tag.getmfpol +-- @see master_fill_policy +-- @tparam[opt=tag.selected()] tag t The tag +-- @treturn string Possible values are +-- "expand" (fill all the available workarea, default one) or +-- "master_width_factor" (fill only an area inside the master width factor) +function tag.getmfpol(t) + util.deprecate("Use t.master_fill_policy instead of awful.tag.getmfpol") + + t = t or ascreen.focused().selected_tag + return tag.getproperty(t, "master_fill_policy") + or beautiful.master_fill_policy + or defaults.master_fill_policy +end + +--- The default number of master windows. +-- +-- @beautiful beautiful.master_count +-- @param integer (default: 1) +-- @see master_count + +--- Set the number of master windows. +-- +-- **Signal:** +-- +-- * *property::nmaster* (deprecated) +-- * *property::master_count* (deprecated) +-- +-- @property master_count +-- @param integer nmaster Only positive values are accepted + +function tag.object.set_master_count(t, nmaster) + if nmaster >= 0 then + tag.setproperty(t, "nmaster", nmaster) + tag.setproperty(t, "master_count", nmaster) + end +end + +function tag.object.get_master_count(t) + return tag.getproperty(t, "master_count") + or beautiful.master_count + or defaults.master_count +end + +--- +-- @deprecated awful.tag.setnmaster +-- @see master_count +-- @param nmaster The number of master windows. +-- @param[opt] t The tag. +function tag.setnmaster(nmaster, t) + util.deprecate("Use t.master_count = nmaster instead of awful.tag.setnmaster") + + tag.object.set_master_count(t or ascreen.focused().selected_tag, nmaster) +end + +--- Get the number of master windows. +-- @deprecated awful.tag.getnmaster +-- @see master_count +-- @param[opt] t The tag. +function tag.getnmaster(t) + util.deprecate("Use t.master_count instead of awful.tag.setnmaster") + + t = t or ascreen.focused().selected_tag + return tag.getproperty(t, "master_count") or 1 +end + +--- Increase the number of master windows. +-- @function awful.tag.incnmaster +-- @see master_count +-- @param add Value to add to number of master windows. +-- @param[opt] t The tag to modify, if null tag.selected() is used. +-- @tparam[opt=false] boolean sensible Limit nmaster based on the number of +-- visible tiled windows? +function tag.incnmaster(add, t, sensible) + t = t or ascreen.focused().selected_tag + + if sensible then + local screen = get_screen(tag.getproperty(t, "screen")) + local ntiled = #screen.tiled_clients + + local nmaster = tag.object.get_master_count(t) + if nmaster > ntiled then + nmaster = ntiled + end + + local newnmaster = nmaster + add + if newnmaster > ntiled then + newnmaster = ntiled + end + tag.object.set_master_count(t, newnmaster) + else + tag.object.set_master_count(t, tag.object.get_master_count(t) + add) + end +end + +--- Set the tag icon. +-- +-- **Signal:** +-- +-- * *property::icon* +-- +-- @property icon +-- @tparam path|surface icon The icon + +-- accessors are implicit. + +--- Set the tag icon +-- @deprecated awful.tag.seticon +-- @see icon +-- @param icon the icon to set, either path or image object +-- @param _tag the tag +function tag.seticon(icon, _tag) + util.deprecate("Use t.icon = icon instead of awful.tag.seticon") + + _tag = _tag or ascreen.focused().selected_tag + tag.setproperty(_tag, "icon", icon) +end + +--- Get the tag icon +-- @deprecated awful.tag.geticon +-- @see icon +-- @param _tag the tag +function tag.geticon(_tag) + util.deprecate("Use t.icon instead of awful.tag.geticon") + + _tag = _tag or ascreen.focused().selected_tag + return tag.getproperty(_tag, "icon") +end + +--- The default number of columns. +-- +-- @beautiful beautiful.column_count +-- @param integer (default: 1) +-- @see column_count + +--- Set the number of columns. +-- +-- **Signal:** +-- +-- * *property::ncol* (deprecated) +-- * *property::column_count* +-- +-- @property column_count +-- @tparam integer ncol Has to be greater than 1 + +function tag.object.set_column_count(t, ncol) + if ncol >= 1 then + tag.setproperty(t, "ncol", ncol) + tag.setproperty(t, "column_count", ncol) + end +end + +function tag.object.get_column_count(t) + return tag.getproperty(t, "column_count") + or beautiful.column_count + or defaults.column_count +end + +--- Set number of column windows. +-- @deprecated awful.tag.setncol +-- @see column_count +-- @param ncol The number of column. +-- @param t The tag to modify, if null tag.selected() is used. +function tag.setncol(ncol, t) + util.deprecate("Use t.column_count = new_index instead of awful.tag.setncol") + + t = t or ascreen.focused().selected_tag + if ncol >= 1 then + tag.setproperty(t, "ncol", ncol) + tag.setproperty(t, "column_count", ncol) + end +end + +--- Get number of column windows. +-- @deprecated awful.tag.getncol +-- @see column_count +-- @param[opt] t The tag. +function tag.getncol(t) + util.deprecate("Use t.column_count instead of awful.tag.getncol") + + t = t or ascreen.focused().selected_tag + return tag.getproperty(t, "column_count") or 1 +end + +--- Increase number of column windows. +-- @function awful.tag.incncol +-- @param add Value to add to number of column windows. +-- @param[opt] t The tag to modify, if null tag.selected() is used. +-- @tparam[opt=false] boolean sensible Limit column_count based on the number +-- of visible tiled windows? +function tag.incncol(add, t, sensible) + t = t or ascreen.focused().selected_tag + + if sensible then + local screen = get_screen(tag.getproperty(t, "screen")) + local ntiled = #screen.tiled_clients + local nmaster = tag.object.get_master_count(t) + local nsecondary = ntiled - nmaster + + local ncol = tag.object.get_column_count(t) + if ncol > nsecondary then + ncol = nsecondary + end + + local newncol = ncol + add + if newncol > nsecondary then + newncol = nsecondary + end + + tag.object.set_column_count(t, newncol) + else + tag.object.set_column_count(t, tag.object.get_column_count(t) + add) + end +end + +--- View no tag. +-- @function awful.tag.viewnone +-- @tparam[opt] int|screen screen The screen. +function tag.viewnone(screen) + screen = screen or ascreen.focused() + local tags = screen.tags + for _, t in pairs(tags) do + t.selected = false + end +end + +--- View a tag by its taglist index. +-- +-- This is equivalent to `screen.tags[i]:view_only()` +-- @function awful.tag.viewidx +-- @see screen.tags +-- @param i The **relative** index to see. +-- @param[opt] screen The screen. +function tag.viewidx(i, screen) + screen = get_screen(screen or ascreen.focused()) + local tags = screen.tags + local showntags = {} + for _, t in ipairs(tags) do + if not tag.getproperty(t, "hide") then + table.insert(showntags, t) + end + end + local sel = screen.selected_tag + tag.viewnone(screen) + for k, t in ipairs(showntags) do + if t == sel then + showntags[util.cycle(#showntags, k + i)].selected = true + end + end + screen:emit_signal("tag::history::update") +end + +--- Get a tag's index in the gettags() table. +-- @deprecated awful.tag.getidx +-- @see index +-- @param query_tag The tag object to find. [selected()] +-- @return The index of the tag, nil if the tag is not found. +function tag.getidx(query_tag) + util.deprecate("Use t.index instead of awful.tag.getidx") + + return tag.object.get_index(query_tag or ascreen.focused().selected_tag) +end + +--- View next tag. This is the same as tag.viewidx(1). +-- @function awful.tag.viewnext +-- @param screen The screen. +function tag.viewnext(screen) + return tag.viewidx(1, screen) +end + +--- View previous tag. This is the same a tag.viewidx(-1). +-- @function awful.tag.viewprev +-- @param screen The screen. +function tag.viewprev(screen) + return tag.viewidx(-1, screen) +end + +--- View only a tag. +-- @function tag.view_only +-- @see selected +function tag.object.view_only(self) + local tags = self.screen.tags + -- First, untag everyone except the viewed tag. + for _, _tag in pairs(tags) do + if _tag ~= self then + _tag.selected = false + end + end + -- Then, set this one to selected. + -- We need to do that in 2 operations so we avoid flickering and several tag + -- selected at the same time. + self.selected = true + capi.screen[self.screen]:emit_signal("tag::history::update") +end + +--- View only a tag. +-- @deprecated awful.tag.viewonly +-- @see tag.view_only +-- @param t The tag object. +function tag.viewonly(t) + util.deprecate("Use t:view_only() instead of awful.tag.viewonly") + + tag.object.view_only(t) +end + +--- View only a set of tags. +-- @function awful.tag.viewmore +-- @param tags A table with tags to view only. +-- @param[opt] screen The screen of the tags. +function tag.viewmore(tags, screen) + screen = get_screen(screen or ascreen.focused()) + local screen_tags = screen.tags + for _, _tag in ipairs(screen_tags) do + if not util.table.hasitem(tags, _tag) then + _tag.selected = false + end + end + for _, _tag in ipairs(tags) do + _tag.selected = true + end + screen:emit_signal("tag::history::update") +end + +--- Toggle selection of a tag +-- @function awful.tag.viewtoggle +-- @see selected +-- @tparam tag t Tag to be toggled +function tag.viewtoggle(t) + t.selected = not t.selected + capi.screen[tag.getproperty(t, "screen")]:emit_signal("tag::history::update") +end + +--- Get tag data table. +-- +-- Do not use. +-- +-- @deprecated awful.tag.getdata +-- @tparam tag _tag The tag. +-- @return The data table. +function tag.getdata(_tag) + return _tag.data.awful_tag_properties +end + +--- Get a tag property. +-- +-- Use `_tag.prop` directly. +-- +-- @deprecated awful.tag.getproperty +-- @tparam tag _tag The tag. +-- @tparam string prop The property name. +-- @return The property. +function tag.getproperty(_tag, prop) + if not _tag then return end -- FIXME: Turn this into an error? + if _tag.data.awful_tag_properties then + return _tag.data.awful_tag_properties[prop] + end +end + +--- Set a tag property. +-- This properties are internal to awful. Some are used to draw taglist, or to +-- handle layout, etc. +-- +-- Use `_tag.prop = value` +-- +-- @deprecated awful.tag.setproperty +-- @param _tag The tag. +-- @param prop The property name. +-- @param value The value. +function tag.setproperty(_tag, prop, value) + if not _tag.data.awful_tag_properties then + _tag.data.awful_tag_properties = {} + end + + if _tag.data.awful_tag_properties[prop] ~= value then + _tag.data.awful_tag_properties[prop] = value + _tag:emit_signal("property::" .. prop) + end +end + +--- Tag a client with the set of current tags. +-- @deprecated awful.tag.withcurrent +-- @param c The client to tag. +function tag.withcurrent(c) + util.deprecate("Use c:to_selected_tags() instead of awful.tag.selectedlist") + + -- It can't use c:to_selected_tags() because awful.tag is loaded before + -- awful.client + + local tags = {} + for _, t in ipairs(c:tags()) do + if get_screen(tag.getproperty(t, "screen")) == get_screen(c.screen) then + table.insert(tags, t) + end + end + if #tags == 0 then + tags = c.screen.selected_tags + end + if #tags == 0 then + tags = c.screen.tags + end + if #tags ~= 0 then + c:tags(tags) + end +end + +local function attached_connect_signal_screen(screen, sig, func) + screen = get_screen(screen) + capi.tag.connect_signal(sig, function(_tag) + if get_screen(tag.getproperty(_tag, "screen")) == screen then + func(_tag) + end + end) +end + +--- Add a signal to all attached tags and all tags that will be attached in the +-- future. When a tag is detached from the screen, its signal is removed. +-- +-- @function awful.tag.attached_connect_signal +-- @screen The screen concerned, or all if nil. +-- @tparam[opt] string Signal +-- @tparam[opt] function Callback +function tag.attached_connect_signal(screen, ...) + if screen then + attached_connect_signal_screen(screen, ...) + else + capi.tag.connect_signal(...) + end +end + +-- Register standard signals. +capi.client.connect_signal("property::screen", function(c) + -- First, the delayed timer is necessary to avoid a race condition with + -- awful.rules. It is also messing up the tags before the user have a chance + -- to set them manually. + timer.delayed_call(function() + local tags, new_tags = c:tags(), {} + + for _, t in ipairs(tags) do + if t.screen == c.screen then + table.insert(new_tags, t) + end + end + + if #new_tags == 0 then + c:emit_signal("request::tag", nil, {reason="screen"}) + elseif #new_tags < #tags then + c:tags(new_tags) + end + end) +end) + +-- Keep track of the number of urgent clients. +local function update_urgent(t, modif) + local count = tag.getproperty(t, "urgent_count") or 0 + count = (count + modif) >= 0 and (count + modif) or 0 + tag.setproperty(t, "urgent" , count > 0) + tag.setproperty(t, "urgent_count", count ) +end + +-- Update the urgent counter when a client is tagged. +local function client_tagged(c, t) + if c.urgent then + update_urgent(t, 1) + end +end + +-- Update the urgent counter when a client is untagged. +local function client_untagged(c, t) + if c.urgent then + update_urgent(t, -1) + end + + if #t:clients() == 0 and tag.getproperty(t, "volatile") then + tag.object.delete(t) + end +end + +-- Count the urgent clients. +local function urgent_callback(c) + for _,t in ipairs(c:tags()) do + update_urgent(t, c.urgent and 1 or -1) + end +end + +capi.client.connect_signal("property::urgent", urgent_callback) +capi.client.connect_signal("untagged", client_untagged) +capi.client.connect_signal("tagged", client_tagged) +capi.tag.connect_signal("request::select", tag.object.view_only) + +--- True when a tagged client is urgent +-- @signal property::urgent +-- @see client.urgent + +--- The number of urgent tagged clients +-- @signal property::urgent_count +-- @see client.urgent + +capi.screen.connect_signal("tag::history::update", tag.history.update) + +capi.screen.connect_signal("removed", function(s) + -- First give other code a chance to move the tag to another screen + for _, t in pairs(s.tags) do + t:emit_signal("request::screen") + end + -- Everything that's left: Tell everyone that these tags go away (other code + -- could e.g. save clients) + for _, t in pairs(s.tags) do + t:emit_signal("removal-pending") + end + -- Give other code yet another change to save clients + for _, c in pairs(capi.client.get(s)) do + c:emit_signal("request::tag", nil, { reason = "screen-removed" }) + end + -- Then force all clients left to go somewhere random + local fallback = nil + for other_screen in capi.screen do + if #other_screen.tags > 0 then + fallback = other_screen.tags[1] + break + end + end + for _, t in pairs(s.tags) do + t:delete(fallback, true) + end + -- If any tag survived until now, forcefully get rid of it + for _, t in pairs(s.tags) do + t.activated = false + + if t.data.awful_tag_properties then + t.data.awful_tag_properties.screen = nil + end + end +end) + +function tag.mt:__call(...) + return tag.new(...) +end + +-- Extend the luaobject +-- `awful.tag.setproperty` currently handle calling the setter method itself +-- while `awful.tag.getproperty`. +object.properties(capi.tag, { + getter_class = tag.object, + setter_class = tag.object, + getter_fallback = tag.getproperty, + setter_fallback = tag.setproperty, +}) + +return setmetatable(tag, tag.mt) + +-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 |