433 lines
15 KiB
Lua
433 lines
15 KiB
Lua
---------------------------------------------------------------------------
|
|
-- Display a monthly or yearly calendar.
|
|
--
|
|
-- 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
|
|
-- @widgetmod 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", "flex_height",
|
|
}
|
|
|
|
--- 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
|
|
|
|
--- Allow cells to have flexible height.
|
|
-- Flexible height allow cells to adapt their height to fill the empty space at the bottom of the widget.
|
|
-- @beautiful beautiful.flex_height
|
|
-- @param boolean Cells can skretch to fill the empty space.
|
|
|
|
--- The calendar date.
|
|
--
|
|
-- E.g.. `{day=21, month=2, year=2005}`, `{month=2, year=2005}, {year=2005}`
|
|
-- @tparam[opt=nil] table|nil date
|
|
-- @tparam number date.year Date year
|
|
-- @tparam number|nil date.month Date month
|
|
-- @tparam number|nil date.day Date day
|
|
-- @propertytype nil The current date.
|
|
-- @property date
|
|
|
|
--- The calendar font.
|
|
--
|
|
-- Choose a monospace font for a better rendering.
|
|
--
|
|
--@DOC_wibox_widget_calendar_font_EXAMPLE@
|
|
--
|
|
-- @tparam[opt="Monospace 10"] font font Font of the calendar
|
|
-- @property font
|
|
-- @usebeautiful beautiful.calendar_font
|
|
|
|
--- The calendar spacing.
|
|
--
|
|
-- The spacing between cells in the month.
|
|
-- The spacing between months in a year calendar is twice this value.
|
|
-- @tparam[opt=5] number spacing Spacing of the grid
|
|
-- @property spacing
|
|
-- @negativeallowed false
|
|
-- @propertyunit pixel
|
|
-- @usebeautiful beautiful.calendar_spacing
|
|
|
|
--- Display the calendar week numbers.
|
|
--
|
|
--@DOC_wibox_widget_calendar_week_numbers_EXAMPLE@
|
|
--
|
|
-- @tparam[opt=false] boolean week_numbers Display week numbers
|
|
-- @property week_numbers
|
|
-- @usebeautiful beautiful.calendar_week_numbers
|
|
|
|
--- Start the week on Sunday.
|
|
--
|
|
--@DOC_wibox_widget_calendar_start_sunday_EXAMPLE@
|
|
--
|
|
-- @tparam[opt=false] boolean start_sunday Start the week on Sunday
|
|
-- @property start_sunday
|
|
-- @usebeautiful beautiful.calendar_start_sunday
|
|
|
|
--- Format the weekdays with three characters instead of two
|
|
--
|
|
--@DOC_wibox_widget_calendar_long_weekdays_EXAMPLE@
|
|
--
|
|
-- @tparam[opt=false] boolean long_weekdays Use three characters for the weekdays instead of two
|
|
-- @property long_weekdays
|
|
-- @usebeautiful beautiful.calendar_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@
|
|
-- @tparam[opt=nil] function|nil fn_embed Function to embed the widget depending on its flag.
|
|
-- @functionparam widget widget
|
|
-- @functionparam string flag The type of widget. It is one of `"header"`, `"monthheader"`,
|
|
-- `"weeknumber"` `"weekday"`, `"focus"`, `"month"` or `"normal"`.
|
|
-- @functionparam table date A table with `day`, `month` and `year` keys.
|
|
-- @functionreturn widget A new widget to insert into the calendar.
|
|
-- @propertytype nil Use an uncustomized `wibox.widget.textbox`.
|
|
-- @property fn_embed
|
|
|
|
--- Allow cells to have flexible height
|
|
--
|
|
--@DOC_wibox_widget_calendar_flex_height_EXAMPLE@
|
|
--
|
|
-- @tparam[opt=false] boolean flex_height Allow flex height.
|
|
-- @property flex_height
|
|
-- @usebeautiful beautiful.flex_height
|
|
|
|
--- 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)
|
|
local w = textbox()
|
|
w:set_markup(text)
|
|
w:set_halign(center and "center" or "right")
|
|
w:set_valign("center")
|
|
w:set_font(font)
|
|
return w
|
|
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 start_row = 3
|
|
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 num_columns = props.week_numbers and 8 or 7
|
|
local start_column = num_columns - 6
|
|
|
|
-- Compute number of rows
|
|
-- There are at least 4 weeks in a month
|
|
local num_rows = 4
|
|
-- On every month but february on non bisextile years
|
|
if last_day.day > 28 then
|
|
-- The number of days span over at least 5 weeks
|
|
num_rows = num_rows + 1
|
|
|
|
-- On month with 30+ days add 1 week if:
|
|
-- - if 30 days and the first day is the last day of the week
|
|
-- - if 31 days and the first days is at least the second to last day
|
|
if column_fday >= 5 then
|
|
if last_day.day == 30 and column_fday == 6 or last_day.day == 31 then
|
|
num_rows = num_rows + 1
|
|
end
|
|
end
|
|
-- If the first day of february is anything but the first day of the week
|
|
elseif column_fday > 1 then
|
|
-- Span over 5 weeks
|
|
num_rows = num_rows + 1
|
|
end
|
|
|
|
-- Create grid layout
|
|
local layout = grid()
|
|
if props.flex_height then
|
|
layout:set_expand(true)
|
|
end
|
|
layout:set_homogeneous(true)
|
|
layout:set_spacing(props.spacing)
|
|
layout:set_forced_num_rows(num_rows)
|
|
layout:set_forced_num_cols(num_columns)
|
|
|
|
--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 = "<b>"..text.."</b>"
|
|
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
|
|
-- @tparam number|string date Year to display.
|
|
-- @treturn widget Grid layout
|
|
local function create_year(props, date)
|
|
-- Create a grid widget with the 12 months
|
|
local in_layout = grid()
|
|
in_layout:set_expand(true)
|
|
in_layout:set_homogeneous(true)
|
|
in_layout:set_spacing(2*props.spacing)
|
|
in_layout:set_forced_num_cols(4)
|
|
in_layout:set_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 = vertical()
|
|
out_layout:set_spacing(2*props.spacing) -- separate header from calendar grid
|
|
out_layout:add(year_header)
|
|
out_layout:add(in_layout)
|
|
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.flex_height = beautiful.calendar_flex_height 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
|
|
-- @constructorfct 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
|
|
-- @constructorfct 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
|