diff --git a/lib/awful/widget/calendar_popup.lua b/lib/awful/widget/calendar_popup.lua
new file mode 100644
index 000000000..a7a9d9ef3
--- /dev/null
+++ b/lib/awful/widget/calendar_popup.lua
@@ -0,0 +1,390 @@
+---------------------------------------------------------------------------
+-- A calendar popup wibox
+--
+-- Display a month or year calendar popup using `calendar_popup.month` or `calendar_popup.year`.
+-- The calendar style can be tweaked by providing tables of style properties at creation:
+-- `style_year`, `style_month`, `style_yearheader`, `style_header`,
+-- `style_weekday`, `style_weeknumber`, `style_normal`, `style_focus` (see `cell_properties`).
+--
+-- The wibox accepts arguments for the calendar widget: `font`, `spacing`, `week_numbers`,
+-- `start_sunday`, `long_weekdays`.
+-- It also accepts the extra arguments `opacity`, `bg`, `screen` and `position`.
+-- `opacity` and `bg` apply to the wibox itself, they are mainly useful to manage opacity
+-- by setting `opacity` for the false opacity or setting `bg="#00000000"` for compositor opacity.
+-- The `screen` argument forces the display of the wibox to this screen (instead of the focused screen by default).
+-- The `position` argument is a two-characters string describing the screen alignment "[vertical][horizontal]",
+-- e.g. "cc", "tr", "bl", ...
+--
+-- The wibox visibility can be changed calling the `toggle` method.
+-- The `attach` method adds mouse bindings to an existing widget in order to toggle the display of the wibox.
+--
+--@DOC_wibox_awidget_defaults_calendar_popup_EXAMPLE@
+--
+-- @author getzze
+-- @copyright 2017 getzze
+-- @classmod awful.widget.calendar_popup
+---------------------------------------------------------------------------
+
+local setmetatable = setmetatable
+local string = string
+local gears = require("gears")
+local wibox = require("wibox")
+local base = require("wibox.widget.base")
+local ascreen = require("awful.screen")
+local abutton = require("awful.button")
+local beautiful = require("beautiful")
+
+local calendar_popup = { offset = 0, mt = {} }
+
+local properties = { "markup", "fg_color", "bg_color", "shape", "padding", "border_width", "border_color", "opacity" }
+local styles = { "year", "month", "yearheader", "monthheader", "header", "weekday", "weeknumber", "normal", "focus" }
+
+
+--- The generic calendar style table.
+--
+-- Each table property can also be defined by `beautiful.calendar_[flag]_[property]=val`.
+-- @beautiful beautiful.calendar_style
+-- @tparam cell_properties table Table of cell style properties
+
+
+--- Cell properties.
+-- @field markup Markup function or format string
+-- @field fg_color Text foreground color
+-- @field bg_color Text background color
+-- @field shape Cell shape
+-- @field padding Cell padding
+-- @field border_width Calendar border width
+-- @field border_color Calendar border color
+-- @field opacity Cell opacity
+-- @table cell_properties
+
+--- Cell types (flags).
+-- @field year Year calendar grid properties table
+-- @field month Month calendar grid properties table
+-- @field yearheader Year header cell properties table
+-- @field header Month header cell properties table (called `monthheader` for a year calendar)
+-- @field weekday Weekday cell properties table
+-- @field weeknumber Weeknumber cell properties table
+-- @field normal Normal day cell properties table
+-- @field focus Current day cell properties table
+-- @table cell_flags
+
+
+
+--- Create a container for the grid layout
+-- @tparam table tprops Table of calendar container properties.
+-- @treturn function Embedding function widget,flag,date -> widget
+local function embed(tprops)
+ local function fn (widget, flag, _)
+ if flag == "monthheader" and not tprops.monthheader then
+ flag = "header"
+ end
+ local props = tprops[flag]
+ -- Markup
+ if flag ~= "year" and flag ~= "month" then
+ local markup = widget:get_text()
+ local m = props.markup
+ if type(m) == "function" then
+ markup = m(markup)
+ elseif type(m) == "string" and string.find(m, "%s", 1, true) then
+ markup = string.format(m, markup)
+ end
+ widget:set_markup(markup)
+ end
+
+ local out = base.make_widget_declarative {
+ {
+ widget,
+ margins = props.padding + props.border_width,
+ widget = wibox.container.margin
+ },
+ shape = props.shape or gears.shape.rectangle,
+ shape_border_color = props.border_color,
+ shape_border_width = props.border_width,
+ fg = props.fg_color,
+ bg = props.bg_color,
+ opacity = props.opacity,
+ widget = wibox.container.background
+ }
+ return out
+ end
+ return fn
+end
+
+
+--- Parse the properties of the cell type and set default values
+-- @tparam string cell The cell type
+-- @tparam table args Table of properties to enforce
+-- @treturn table The properties table
+local function parse_cell_options(cell, args)
+ args = args or {}
+ local props = {}
+ local bl_style = beautiful.calendar_style or {}
+
+ for _, prop in ipairs(properties) do
+ local default
+ if prop == 'markup' then
+ default = cell == "focus" and string.format('%s',
+ beautiful.fg_focus, beautiful.bg_focus, "%s")
+ elseif prop == 'fg_color' then
+ default = cell == "focus" and beautiful.fg_focus or beautiful.fg_normal
+ elseif prop == 'bg_color' then
+ default = cell == "focus" and beautiful.bg_focus or beautiful.bg_normal
+ elseif prop == 'padding' then
+ default = 2
+ elseif prop == 'opacity' then
+ default = 1
+ elseif prop == 'shape' then
+ default = nil
+ elseif prop == 'border_width' then
+ default = beautiful.border_width or 0
+ elseif prop == 'border_color' then
+ default = beautiful.border_normal or beautiful.fg_normal
+ end
+
+ -- Get default
+ props[prop] = args[prop] or beautiful["calendar_" .. cell .. "_" .. prop] or bl_style[prop] or default
+ end
+ return props
+end
+
+--- Parse the properties
+-- @tparam table args Table of properties
+-- @treturn table The properties table
+local function parse_all_options(args)
+ args = args or {}
+ local props = {}
+
+ for _, cell in pairs(styles) do
+ if cell~="monthheader" or args.style_monthheader then
+ props[cell] = parse_cell_options(cell, args["style_" .. cell])
+ end
+ end
+ return props
+end
+
+
+--- Make the geometry of a wibox
+-- @tparam widget widget Calendar widget
+-- @tparam object screen Screen where to display the calendar (default to focused)
+-- @tparam string position Two characters position of the calendar (default "cc")
+-- @treturn number,number,number,number Geometry of the calendar, list of x, y, width, height
+local function get_geometry(widget, screen, position)
+ local pos, s = position or "cc", screen or ascreen.focused()
+ local wa = s.workarea
+ local width, height = widget:fit({screen=s, dpi=beautiful.xresources.get_dpi(s)}, wa.width, wa.height)
+
+ width = width < wa.width and width or wa.width
+ height = height < wa.height and height or wa.height
+
+
+ -- Set to position: pos = tl, tc, tr
+ -- cl, cc, cr
+ -- bl, bc, br
+ local x,y
+ if pos:sub(1,1) == "t" then
+ y = wa.y
+ elseif pos:sub(1,1) == "b" then
+ y = wa.y + wa.height - height
+ else --if pos:sub(1,1) == "c" then
+ y = wa.y + math.floor((wa.height - height) / 2)
+ end
+ if pos:sub(2,2) == "l" then
+ x = wa.x
+ elseif pos:sub(2,2) == "r" then
+ x = wa.x + wa.width - width
+ else --if pos:sub(2,2) == "c" then
+ x = wa.x + math.floor((wa.width - width) / 2)
+ end
+
+ return {x=x, y=y, width=width, height=height}
+end
+
+--- Call the calendar with offset
+-- @tparam number offset Offset with respect to current month or year
+-- @tparam string position Two-character position of the calendar in the screen
+-- @tparam screen screen Screen where to display the calendar
+-- @treturn wibox The wibox calendar
+function calendar_popup:call_calendar(offset, position, screen)
+ local inc_offset, pos, s = offset or 0, position or self.position, screen or self.screen or ascreen.focused()
+ self.position = pos -- remember last position when changing offset
+
+ self.offset = inc_offset ~= 0 and self.offset + inc_offset or 0
+
+ local widget = self:get_widget()
+ local raw_date = os.date("*t")
+ local date = {day=raw_date.day, month=raw_date.month, year=raw_date.year}
+ if widget._private.type == "month" and self.offset ~= 0 then
+ raw_date.month = raw_date.month + self.offset
+ raw_date = os.date("*t", os.time(raw_date))
+ date = {month=raw_date.month, year=raw_date.year}
+ elseif widget._private.type == "year" then
+ date = {year=raw_date.year + self.offset}
+ end
+
+ -- set date and screen before updating geometry
+ widget:set_date(date)
+ self:set_screen(s)
+ -- update geometry (depends on date and screen)
+ self:geometry(get_geometry(widget, s, pos))
+ return self
+end
+
+
+--- Toggle calendar visibility
+function calendar_popup:toggle()
+ self:call_calendar(0)
+ self.visible = not self.visible
+end
+
+
+--- Attach the calendar to a widget to display at a specific position.
+--
+-- local mytextclock = wibox.widget.textclock()
+-- local month_calendar = calendar.month()
+-- month_calendar:attach(mytextclock, 'tr')
+--
+-- @param widget Widget to attach the calendar
+-- @tparam[opt="tr"] string position Two characters string defining the position on the screen
+-- @treturn wibox The wibox calendar
+function calendar_popup:attach(widget, position)
+ position = position or "tr"
+ widget:buttons(gears.table.join(
+ abutton({ }, 1, function ()
+ self:call_calendar(0, position)
+ self.visible = not self.visible
+ end),
+ abutton({ }, 4, function () self:call_calendar(-1) end),
+ abutton({ }, 5, function () self:call_calendar( 1) end)
+ ))
+ return self
+end
+
+
+--- Return a new calendar wibox by type.
+--
+-- A calendar widget displaying a `month` or a `year`
+-- @tparam string caltype Type of calendar `month` or `year`
+-- @tparam table args Properties of the widget
+-- @tparam string args.position Two-character position of the calendar in the screen
+-- @tparam screen args.screen Screen where to display the calendar
+-- @tparam number args.opacity Wibox opacity
+-- @tparam string args.bg Wibox background color
+-- @tparam string args.font Calendar font
+-- @tparam number args.spacing Calendar spacing
+-- @tparam boolean args.week_numbers Show weeknumbers
+-- @tparam boolean args.start_sunday Start week on Sunday
+-- @tparam boolean args.long_weekdays Format the weekdays with three characters instead of two
+-- @tparam table args.year_style Container style for the year calendar (see `cell_properties`)
+-- @tparam table args.month_style Container style for the month calendar (see `cell_properties`)
+-- @tparam table args.yearheader_style Cell style for the year calendar header (see `cell_properties`)
+-- @tparam table args.header_style Cell style for the month calendar header (see `cell_properties`)
+-- @tparam table args.weekday_style Cell style for the weekday cells (see `cell_properties`)
+-- @tparam table args.weeknumber_style Cell style for the weeknumber cells (see `cell_properties`)
+-- @tparam table args.normal_style Cell style for the normal day cells (see `cell_properties`)
+-- @tparam table args.focus_style Cell style for the current day cell (see `cell_properties`)
+-- @treturn wibox A wibox containing the calendar
+local function get_cal_wibox(caltype, args)
+ args = args or {}
+
+ local ret = wibox{ ontop = true,
+ opacity = args.opacity or 1,
+ bg = args.bg
+ }
+ gears.table.crush(ret, calendar_popup, false)
+
+ ret.offset = 0
+ ret.position = args.position or "cc"
+ ret.screen = args.screen
+
+ local widget = wibox.widget {
+ font = args.font,
+ spacing = args.spacing,
+ week_numbers = args.week_numbers,
+ start_sunday = args.start_sunday,
+ long_weekdays = args.long_weekdays,
+ fn_embed = embed(parse_all_options(args)),
+ widget = caltype == "year" and wibox.widget.calendar.year or wibox.widget.calendar.month
+ }
+ ret:set_widget(widget)
+
+ ret:buttons(gears.table.join(
+ abutton({ }, 1, function () ret.visible=false end),
+ abutton({ }, 3, function () ret.visible=false end),
+ abutton({ }, 4, function () ret:call_calendar(-1) end),
+ abutton({ }, 5, function () ret:call_calendar( 1) end)
+ ))
+ return ret
+end
+
+--- A month calendar wibox.
+--
+-- It is highly customizable using the same options as for the widgets.
+-- The options are set once and for all at creation, though.
+--
+--@DOC_wibox_awidget_calendar_month_wibox_EXAMPLE@
+--
+-- local mytextclock = wibox.widget.textclock()
+-- month_calendar:attach( mytextclock, "tr" )
+--
+-- @tparam table args Properties of the widget
+-- @tparam string args.position Two-character position of the calendar in the screen
+-- @tparam screen args.screen Screen where to display the calendar
+-- @tparam number args.opacity Wibox opacity
+-- @tparam string args.bg Wibox background color
+-- @tparam string args.font Calendar font
+-- @tparam number args.spacing Calendar spacing
+-- @tparam boolean args.week_numbers Show weeknumbers
+-- @tparam boolean args.start_sunday Start week on Sunday
+-- @tparam boolean args.long_weekdays Format the weekdays with three characters instead of two
+-- @tparam table args.month_style Container style for the month calendar (see `cell_properties`)
+-- @tparam table args.header_style Cell style for the month calendar header (see `cell_properties`)
+-- @tparam table args.weekday_style Cell style for the weekday cells (see `cell_properties`)
+-- @tparam table args.weeknumber_style Cell style for the weeknumber cells (see `cell_properties`)
+-- @tparam table args.normal_style Cell style for the normal day cells (see `cell_properties`)
+-- @tparam table args.focus_style Cell style for the current day cell (see `cell_properties`)
+-- @treturn wibox A wibox containing the calendar
+-- @function awful.calendar.month
+function calendar_popup.month(args)
+ return get_cal_wibox("month", args)
+end
+
+
+--- A year calendar wibox.
+--
+-- It is highly customizable using the same options as for the widgets.
+-- The options are set once and for all at creation, though.
+--
+--@DOC_wibox_awidget_calendar_year_wibox_EXAMPLE@
+--
+-- globalkeys = gears.table.join(globalkeys, awful.key(
+-- { modkey, "Control" }, "c", function () year_calendar:toggle() end))
+--
+-- @tparam table args Properties of the widget
+-- @tparam string args.position Two-character position of the calendar in the screen
+-- @tparam screen args.screen Screen where to display the calendar
+-- @tparam number args.opacity Wibox opacity
+-- @tparam string args.bg Wibox background color
+-- @tparam string args.font Calendar font
+-- @tparam number args.spacing Calendar spacing
+-- @tparam boolean args.week_numbers Show weeknumbers
+-- @tparam boolean args.start_sunday Start week on Sunday
+-- @tparam boolean args.long_weekdays Format the weekdays with three characters instead of two
+-- @tparam table args.year_style Container style for the year calendar (see `cell_properties`)
+-- @tparam table args.month_style Container style for the month calendar (see `cell_properties`).
+-- This field can also be called `monthheader_style`.
+-- @tparam table args.yearheader_style Cell style for the year calendar header (see `cell_properties`)
+-- @tparam table args.header_style Cell style for the month calendar header (see `cell_properties`)
+-- @tparam table args.weekday_style Cell style for the weekday cells (see `cell_properties`)
+-- @tparam table args.weeknumber_style Cell style for the weeknumber cells (see `cell_properties`)
+-- @tparam table args.normal_style Cell style for the normal day cells (see `cell_properties`)
+-- @tparam table args.focus_style Cell style for the current day cell (see `cell_properties`)
+-- @treturn wibox A wibox containing the calendar
+-- @function awful.calendar.year
+function calendar_popup.year(args)
+ return get_cal_wibox("year", args)
+end
+
+return setmetatable(calendar_popup, calendar_popup.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/awful/widget/init.lua b/lib/awful/widget/init.lua
index ca0ed6174..ab7e6edeb 100644
--- a/lib/awful/widget/init.lua
+++ b/lib/awful/widget/init.lua
@@ -21,6 +21,7 @@ return
watch = require("awful.widget.watch");
only_on_screen = require("awful.widget.only_on_screen");
clienticon = require("awful.widget.clienticon");
+ calendar_popup = require("awful.widget.calendar_popup");
}
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/calendar.lua b/lib/wibox/widget/calendar.lua
new file mode 100644
index 000000000..aabf35016
--- /dev/null
+++ b/lib/wibox/widget/calendar.lua
@@ -0,0 +1,385 @@
+---------------------------------------------------------------------------
+-- A calendar widget
+--
+-- This module defines two widgets: a month calendar and a year calendar
+--
+-- The two widgets have a `date` property, in the form of
+-- a table {day=[number|nil], month=[number|nil], year=[number]}.
+--
+-- The `year` widget displays the whole specified year, e.g. {year=2006}.
+--
+-- The `month` widget displays the calendar for the specified month, e.g. {month=12, year=2006},
+-- highlighting the specified day if the day is provided in the date, e.g. {day=22, month=12, year=2006}.
+--
+-- Cell and container styles can be overridden using the `fn_embed` callback function
+-- which is called before adding the widgets to the layouts. The `fn_embed` function
+-- takes three arguments, the original widget, the flag (`string` used to identified the widget)
+-- and the date (`table`).
+-- It returns another widget, embedding (and modifying) the original widget.
+--
+--@DOC_wibox_widget_defaults_calendar_EXAMPLE@
+--
+-- @author getzze
+-- @copyright 2017 getzze
+-- @classmod wibox.widget.calendar
+---------------------------------------------------------------------------
+
+
+local setmetatable = setmetatable
+local string = string
+local gtable = require("gears.table")
+local vertical = require("wibox.layout.fixed").vertical
+local grid = require("wibox.layout.grid")
+local textbox = require("wibox.widget.textbox")
+local bgcontainer = require("wibox.container.background")
+local base = require("wibox.widget.base")
+local beautiful = require("beautiful")
+
+local calendar = { mt = {} }
+
+local properties = { "date", "font", "spacing", "week_numbers", "start_sunday", "long_weekdays", "fn_embed" }
+
+
+--- The calendar font.
+-- @beautiful beautiful.calendar_font
+-- @tparam string font Font of the calendar
+
+--- The calendar spacing.
+-- @beautiful beautiful.calendar_spacing
+-- @tparam number spacing Spacing of the grid (twice this value for inter-month spacing)
+
+--- Display the calendar week numbers.
+-- @beautiful beautiful.calendar_week_numbers
+-- @param boolean Display week numbers
+
+--- Start the week on Sunday.
+-- @beautiful beautiful.calendar_start_sunday
+-- @param boolean Start the week on Sunday
+
+--- Format the weekdays with three characters instead of two
+-- @beautiful beautiful.calendar_long_weekdays
+-- @param boolean Use three characters for the weekdays instead of two
+
+--- The calendar date.
+--
+-- A table representing the date {day=[number|nil], month=[number|nil], year=[number]}.
+--
+-- E.g.. {day=21, month=2, year=2005}, {month=2, year=2005}, {year=2005}
+-- @tparam date table Date table.
+-- @tparam number date.year Date year
+-- @tparam number|nil date.month Date month
+-- @tparam number|nil date.day Date day
+-- @property date
+
+--- The calendar font.
+--
+-- Choose a monospace font for a better rendering.
+--@DOC_wibox_widget_calendar_font_EXAMPLE@
+-- @param[opt="Monospace 10"] string Font of the calendar
+-- @property font
+
+--- The calendar spacing.
+--
+-- The spacing between cells in the month.
+-- The spacing between months in a year calendar is twice this value.
+-- @param[opt=5] number Spacing of the grid
+-- @property spacing
+
+--- Display the calendar week numbers.
+--
+--@DOC_wibox_widget_calendar_week_numbers_EXAMPLE@
+-- @param[opt=false] boolean Display week numbers
+-- @property week_numbers
+
+--- Start the week on Sunday.
+--
+--@DOC_wibox_widget_calendar_start_sunday_EXAMPLE@
+-- @param[opt=false] boolean Start the week on Sunday
+-- @property start_sunday
+
+--- Format the weekdays with three characters instead of two
+--
+--@DOC_wibox_widget_calendar_long_weekdays_EXAMPLE@
+-- @param[opt=false] boolean Use three characters for the weekdays instead of two
+-- @property long_weekdays
+
+--- The widget encapsulating function.
+--
+-- Function that takes a widget, flag (`string`) and date (`table`) as argument
+-- and returns a widget encapsulating the input widget.
+--
+-- Default value: function (widget, flag, date) return widget end
+--
+-- It is used to add a container to the grid layout and to the cells:
+--
+--@DOC_wibox_widget_calendar_fn_embed_cell_EXAMPLE@
+-- @param function Function to embed the widget depending on its flag
+-- @property fn_embed
+
+
+--- Make a textbox
+-- @tparam string text Text of the textbox
+-- @tparam string font Font of the text
+-- @tparam boolean center Center the text horizontally
+-- @treturn wibox.widget.textbox
+local function make_cell(text, font, center)
+ return base.make_widget_declarative {
+ markup = text,
+ align = center and "center" or "right",
+ valign = 'center',
+ font = font,
+ widget = textbox
+ }
+end
+
+--- Create a grid layout with the month calendar
+-- @tparam table props Table of calendar properties
+-- @tparam table date Date table
+-- @tparam number date.year Date year
+-- @tparam number date.month Date month
+-- @tparam number|nil date.day Date day
+-- @treturn widget Grid layout
+local function create_month(props, date)
+ local num_rows = 8
+ local num_columns = props.week_numbers and 8 or 7
+
+ -- Create grid layout
+ local layout = base.make_widget_declarative{
+ layout = grid,
+ expand = true,
+ homogeneous = true,
+ spacing = props.spacing,
+ forced_num_rows = num_rows,
+ forced_num_cols = num_columns,
+ }
+
+ local start_row = 3
+ local start_column = num_columns - 6
+ local week_start = props.start_sunday and 1 or 2
+ local last_day = os.date("*t", os.time{year=date.year, month=date.month+1, day=0})
+ local month_days = last_day.day
+ local column_fday = (last_day.wday - month_days + 1 - week_start ) % 7
+
+ --local flags = {"header", "weekdays", "weeknumber", "normal", "focus"}
+ local cell_date, t, i, j, w, flag, text
+
+ -- Header
+ flag = "header"
+ t = os.time{year=date.year, month=date.month, day=1}
+ if props.subtype=="monthheader" then
+ flag = "monthheader"
+ text = os.date("%B", t)
+ else
+ text = os.date("%B %Y", t)
+ end
+ w = props.fn_embed(make_cell(text, props.font, true), flag, date)
+ layout:add_widget_at(w, 1, 1, 1, num_columns)
+
+ -- Days
+ i = start_row
+ j = column_fday + start_column
+ local current_week = nil
+ local drawn_weekdays = 0
+ for d=1, month_days do
+ cell_date = {year=date.year, month=date.month, day=d}
+ t = os.time(cell_date)
+ -- Week number
+ if props.week_numbers then
+ text = os.date("%V", t)
+ if tonumber(text) ~= current_week then
+ flag = "weeknumber"
+ w = props.fn_embed(make_cell(text, props.font), flag, cell_date)
+ layout:add_widget_at(w, i, 1, 1, 1)
+ current_week = tonumber(text)
+ end
+ end
+ -- Week days
+ if drawn_weekdays < 7 then
+ flag = "weekday"
+ text = os.date("%a", t)
+ if not props.long_weekdays then
+ text = string.sub(text, 1, 2)
+ end
+ w = props.fn_embed(make_cell(text, props.font), flag, cell_date)
+ layout:add_widget_at(w, 2, j, 1, 1)
+ drawn_weekdays = drawn_weekdays +1
+ end
+ -- Normal day
+ flag = "normal"
+ text = string.format("%2d", d)
+ -- Focus day
+ if date.day == d then
+ flag = "focus"
+ text = ""..text..""
+ end
+ w = props.fn_embed(make_cell(text, props.font), flag, cell_date)
+ layout:add_widget_at(w, i, j, 1, 1)
+
+ -- find next cell
+ i,j = layout:get_next_empty(i,j)
+ if j < start_column then j = start_column end
+ end
+ return props.fn_embed(layout, "month", date)
+end
+
+
+--- Create a grid layout for the year calendar
+-- @tparam table props Table of year calendar properties
+-- @param date Year to display (number or string)
+-- @treturn widget Grid layout
+local function create_year(props, date)
+ -- Create a grid widget with the 12 months
+ local in_layout = base.make_widget_declarative{
+ layout = grid,
+ expand = true,
+ homogeneous = true,
+ spacing = 2*props.spacing,
+ forced_num_cols = 4,
+ forced_num_rows = 3,
+ }
+
+ local month_date
+ local current_date = os.date("*t")
+
+ for month=1,12 do
+ if date.year == current_date.year and month == current_date.month then
+ month_date = {day=current_date.day, month=current_date.month, year=current_date.year}
+ else
+ month_date = {month=month, year=date.year}
+ end
+ in_layout:add(create_month(props, month_date))
+ end
+
+ -- Create a vertical layout
+ local flag, text = "yearheader", string.format("%s", date.year)
+ local year_header = props.fn_embed(make_cell(text, props.font, true), flag, date)
+ local out_layout = base.make_widget_declarative{
+ year_header,
+ in_layout,
+ spacing = 2*props.spacing, -- separate header from calendar grid
+ layout = vertical
+ }
+ return props.fn_embed(out_layout, "year", date)
+end
+
+
+--- Set the container to the current date
+-- @param self Widget to update
+local function fill_container(self)
+ local date = self._private.date
+ if date then
+ -- Create calendar grid
+ if self._private.type == "month" then
+ self._private.container:set_widget(create_month(self._private, date))
+ elseif self._private.type == "year" then
+ self._private.container:set_widget(create_year(self._private, date))
+ end
+ else
+ self._private.container:set_widget(nil)
+ end
+ self:emit_signal("widget::layout_changed")
+end
+
+
+-- Set the calendar date
+function calendar:set_date(date)
+ if date ~= self._private.date then
+ self._private.date = date
+ -- (Re)create calendar grid
+ fill_container(self)
+ end
+end
+
+
+-- Build properties function
+for _, prop in ipairs(properties) do
+ -- setter
+ if not calendar["set_" .. prop] then
+ calendar["set_" .. prop] = function(self, value)
+ if (string.sub(prop,1,3)=="fn_" and type(value) == "function") or self._private[prop] ~= value then
+ self._private[prop] = value
+ -- (Re)create calendar grid
+ fill_container(self)
+ end
+ end
+ end
+ -- getter
+ if not calendar["get_" .. prop] then
+ calendar["get_" .. prop] = function(self)
+ return self._private[prop]
+ end
+ end
+end
+
+
+--- Return a new calendar widget by type.
+--
+-- @tparam string type Type of the calendar, `year` or `month`
+-- @tparam table date Date of the calendar
+-- @tparam number date.year Date year
+-- @tparam number|nil date.month Date month
+-- @tparam number|nil date.day Date day
+-- @tparam[opt="Monospace 10"] string font Font of the calendar
+-- @treturn widget The calendar widget
+local function get_calendar(type, date, font)
+ local ct = bgcontainer()
+ local ret = base.make_widget(ct, "calendar", {enable_properties = true})
+ gtable.crush(ret, calendar, true)
+
+ ret._private.type = type
+ ret._private.container = ct
+
+ -- default values
+ ret._private.date = date
+ ret._private.font = font or beautiful.calendar_font or "Monospace 10"
+
+ ret._private.spacing = beautiful.calendar_spacing or 5
+ ret._private.week_numbers = beautiful.calendar_week_numbers or false
+ ret._private.start_sunday = beautiful.calendar_start_sunday or false
+ ret._private.long_weekdays = beautiful.calendar_long_weekdays or false
+ ret._private.fn_embed = function (w, _) return w end
+
+ -- header specific
+ ret._private.subtype = type=="year" and "monthheader" or "fullheader"
+
+ fill_container(ret)
+ return ret
+end
+
+--- A month calendar widget.
+--
+-- A calendar widget is a grid containing the calendar for one month.
+-- If the day is specified in the date, its cell is highlighted.
+--
+--@DOC_wibox_widget_calendar_month_EXAMPLE@
+-- @tparam table date Date of the calendar
+-- @tparam number date.year Date year
+-- @tparam number date.month Date month
+-- @tparam number|nil date.day Date day
+-- @tparam[opt="Monospace 10"] string font Font of the calendar
+-- @treturn widget The month calendar widget
+-- @function wibox.widget.calendar.month
+function calendar.month(date, font)
+ return get_calendar("month", date, font)
+end
+
+--- A year calendar widget.
+--
+-- A calendar widget is a grid containing the calendar for one year.
+--
+--@DOC_wibox_widget_calendar_year_EXAMPLE@
+-- @tparam table date Date of the calendar
+-- @tparam number date.year Date year
+-- @tparam number|nil date.month Date month
+-- @tparam number|nil date.day Date day
+-- @tparam[opt="Monospace 10"] string font Font of the calendar
+-- @treturn widget The year calendar widget
+-- @function wibox.widget.calendar.year
+function calendar.year(date, font)
+ return get_calendar("year", date, font)
+end
+
+
+return setmetatable(calendar, calendar.mt)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
diff --git a/lib/wibox/widget/init.lua b/lib/wibox/widget/init.lua
index f33ce3451..04950b498 100644
--- a/lib/wibox/widget/init.lua
+++ b/lib/wibox/widget/init.lua
@@ -19,6 +19,7 @@ local widget = {
checkbox = require("wibox.widget.checkbox");
piechart = require("wibox.widget.piechart");
slider = require("wibox.widget.slider");
+ calendar = require("wibox.widget.calendar");
}
setmetatable(widget, {
diff --git a/tests/examples/wibox/widget/calendar/fn_embed_cell.lua b/tests/examples/wibox/widget/calendar/fn_embed_cell.lua
new file mode 100644
index 000000000..91155e8c5
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/fn_embed_cell.lua
@@ -0,0 +1,84 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local gears = require("gears") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local styles = {}
+ local function rounded_shape(size, partial)
+ if partial then
+ return function(cr, width, height)
+ gears.shape.partially_rounded_rect(cr, width, height,
+ false, true, false, true, 5)
+ end
+ else
+ return function(cr, width, height)
+ gears.shape.rounded_rect(cr, width, height, size)
+ end
+ end
+ end
+ styles.month = { padding = 5,
+ bg_color = "#555555",
+ border_width = 2,
+ shape = rounded_shape(10)
+ }
+ styles.normal = { shape = rounded_shape(5) }
+ styles.focus = { fg_color = "#000000",
+ bg_color = "#ff9800",
+ markup = function(t) return '' .. t .. '' end,
+ shape = rounded_shape(5, true)
+ }
+ styles.header = { fg_color = "#de5e1e",
+ markup = function(t) return '' .. t .. '' end,
+ shape = rounded_shape(10)
+ }
+ styles.weekday = { fg_color = "#7788af",
+ markup = function(t) return '' .. t .. '' end,
+ shape = rounded_shape(5)
+ }
+
+ local function decorate_cell(widget, flag, date)
+ if flag=="monthheader" and not styles.monthheader then
+ flag = "header"
+ end
+ local props = styles[flag] or {}
+ if props.markup and widget.get_text and widget.set_markup then
+ widget:set_markup(props.markup(widget:get_text()))
+ end
+
+ -- Change bg color for weekends
+ local d = {year=date.year, month=(date.month or 1), day=(date.day or 1)}
+ local weekday = tonumber(os.date("%w", os.time(d)))
+ local default_bg = (weekday==0 or weekday==6) and "#232323" or "#383838"
+
+ local ret = wibox.widget {
+ {
+ widget,
+ margins = (props.padding or 2) + (props.border_width or 0),
+ widget = wibox.container.margin
+ },
+ shape = props.shape,
+ shape_border_color = props.border_color or "#b9214f",
+ shape_border_width = props.border_width or 0,
+ fg = props.fg_color or "#999999",
+ bg = props.bg_color or default_bg,
+ widget = wibox.container.background
+ }
+ return ret
+ end
+
+ local cal = wibox.widget {
+ date = os.date("*t"),
+ fn_embed = decorate_cell,
+ widget = wibox.widget.calendar.month
+ }
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/font.lua b/tests/examples/wibox/widget/calendar/font.lua
new file mode 100644
index 000000000..5c42571fa
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/font.lua
@@ -0,0 +1,17 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("sans 12") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget.calendar.month(
+ os.date("*t"), "sans 12")
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/long_weekdays.lua b/tests/examples/wibox/widget/calendar/long_weekdays.lua
new file mode 100644
index 000000000..a4a671bc2
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/long_weekdays.lua
@@ -0,0 +1,21 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget {
+ date = os.date("*t"),
+ font = "Monospace 10",
+ long_weekdays = true,
+ widget = wibox.widget.calendar.month
+ }
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/month.lua b/tests/examples/wibox/widget/calendar/month.lua
new file mode 100644
index 000000000..38167f28a
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/month.lua
@@ -0,0 +1,51 @@
+local parent = ... --DOC_HIDE_ALL
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+local date = os.date("*t") --DOC_HIDE
+
+ local w = wibox.widget {
+ {
+ {
+ {
+ text = '{day='..date.day..', month='..date.month..',\n year='..date.year..'}',
+ align = 'center',
+ widget = wibox.widget.textbox
+ },
+ border_width = 2,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ wibox.widget.calendar.month({day=date.day, month=date.month, year=date.year}),
+ spacing = 10,
+ widget = wibox.layout.fixed.vertical
+ },
+ {
+ {
+ {
+ text = '{month='..date.month..',\n year='..date.year..'}',
+ align = 'center',
+ widget = wibox.widget.textbox
+ },
+ border_width = 2,
+ bg = beautiful.bg_normal,
+ widget = wibox.container.background
+ },
+ wibox.widget.calendar.month({month=date.month, year=date.year}),
+ spacing = 10,
+ widget = wibox.layout.fixed.vertical
+ },
+ spacing = 20,
+ widget = wibox.layout.flex.horizontal
+ }
+
+parent:add(w) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/start_sunday.lua b/tests/examples/wibox/widget/calendar/start_sunday.lua
new file mode 100644
index 000000000..346b587b1
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/start_sunday.lua
@@ -0,0 +1,21 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget {
+ date = os.date("*t"),
+ font = "Monospace 10",
+ start_sunday = true,
+ widget = wibox.widget.calendar.month
+ }
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/week_numbers.lua b/tests/examples/wibox/widget/calendar/week_numbers.lua
new file mode 100644
index 000000000..b319a7564
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/week_numbers.lua
@@ -0,0 +1,21 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget {
+ date = os.date("*t"),
+ font = "Monospace 10",
+ week_numbers = true,
+ widget = wibox.widget.calendar.month
+ }
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/examples/wibox/widget/calendar/year.lua b/tests/examples/wibox/widget/calendar/year.lua
new file mode 100644
index 000000000..4274a0e28
--- /dev/null
+++ b/tests/examples/wibox/widget/calendar/year.lua
@@ -0,0 +1,25 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget {
+ date = os.date("*t"),
+ font = "Monospace 8",
+ spacing = 2,
+ week_numbers = false,
+ start_sunday = false,
+ widget = wibox.widget.calendar.year
+ }
+
+parent:add(cal) --DOC_HIDE
+local w,h = parent:fit({dpi=96}, 9999, 9999) --DOC_HIDE
+return w+30, h --DOC_HIDE
diff --git a/tests/examples/wibox/widget/defaults/calendar.lua b/tests/examples/wibox/widget/defaults/calendar.lua
new file mode 100644
index 000000000..ea57daad9
--- /dev/null
+++ b/tests/examples/wibox/widget/defaults/calendar.lua
@@ -0,0 +1,16 @@
+local parent = ... --DOC_HIDE
+local wibox = require("wibox") --DOC_HIDE
+local beautiful = require( "beautiful" ) --DOC_HIDE
+local Pango = require("lgi").Pango --DOC_HIDE
+
+-- Beautiful fake get_font --DOC_HIDE
+local f = Pango.FontDescription.from_string("monospace 10") --DOC_HIDE
+beautiful.get_font = function() return f end --DOC_HIDE
+
+-- Fake beautiful theme --DOC_HIDE
+beautiful.fg_focus = "#ff9800" --DOC_HIDE
+beautiful.bg_focus = "#b9214f" --DOC_HIDE
+
+ local cal = wibox.widget.calendar.month(os.date("*t"))
+
+parent:add(cal) --DOC_HIDE
diff --git a/tests/test-awful-widget-calendar_popup.lua b/tests/test-awful-widget-calendar_popup.lua
new file mode 100644
index 000000000..d432b5e7c
--- /dev/null
+++ b/tests/test-awful-widget-calendar_popup.lua
@@ -0,0 +1,71 @@
+--- Test for awful.widget.calendar
+
+local runner = require("_runner")
+local awful = require("awful")
+local calendar = require("awful.widget.calendar_popup")
+
+local wa = awful.screen.focused().workarea
+
+local cmonth = calendar.month()
+local cyear = calendar.year()
+local current_date = os.date("*t")
+local day, month, year = current_date.day, current_date.month, current_date.year
+
+local steps = {
+ -- Check center geometry
+ function(count)
+ if count == 1 then
+ cmonth:call_calendar(0, 'cc')
+ else
+ local geo = cmonth:geometry()
+ assert( math.abs((wa.x + wa.width/2.0) - (geo.x + geo.width/2.0)) < 2 )
+ assert( math.abs((wa.y + wa.height/2.0) - (geo.y + geo.height/2.0)) < 2 )
+ return true
+ end
+ end,
+ -- Check top-right geometry
+ function(count)
+ if count == 1 then
+ cmonth:call_calendar(0, 'tr')
+ else
+ local geo = cmonth:geometry()
+ assert(wa.x + wa.width == geo.x + geo.width)
+ assert(wa.y == geo.y)
+ return true
+ end
+ end,
+ -- Check visibility
+ function()
+ local v = cyear.visible
+ cyear:toggle()
+ assert(v == not cyear.visible)
+ return true
+ end,
+ -- Check current date
+ function()
+ local mdate = cmonth:get_widget():get_date()
+ assert(mdate.day==day and mdate.month==month and mdate.year==year)
+ local ydate = cyear:get_widget():get_date()
+ assert(ydate.year==year)
+ return true
+ end,
+ -- Check new date
+ function(count)
+ if count == 1 then
+ -- Increment
+ cmonth:call_calendar(1)
+ cyear:call_calendar(-1)
+ else
+ local mdate = cmonth:get_widget():get_date()
+ assert( mdate.day==nil and
+ ((mdate.month==month+1 and mdate.year==year) or (mdate.month==1 and mdate.year==year+1)) )
+ local ydate = cyear:get_widget():get_date()
+ assert(ydate.year==year-1)
+ return true
+ end
+ end,
+
+}
+runner.run_steps(steps)
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80