diff --git a/lib/awful/widget/calendar_popup.lua b/lib/awful/widget/calendar_popup.lua new file mode 100644 index 00000000..a7a9d9ef --- /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 ca0ed617..ab7e6ede 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 00000000..aabf3501 --- /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 f33ce345..04950b49 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 00000000..91155e8c --- /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 00000000..5c42571f --- /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 00000000..a4a671bc --- /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 00000000..38167f28 --- /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 00000000..346b587b --- /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 00000000..b319a756 --- /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 00000000..4274a0e2 --- /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 00000000..ea57daad --- /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 00000000..d432b5e7 --- /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