From bf01d96f924c6f8bc76e8ae47f04c2ab4dffac14 Mon Sep 17 00:00:00 2001 From: "geortz@gmail.com" Date: Wed, 3 Sep 2014 21:18:16 +0000 Subject: [PATCH] merged/adapted code for Neels Homfeyr's sparse layout: - sparse layout - sparse style - german/berlin holidays git-svn-id: https://callirhoe.googlecode.com/svn/branches/next@49 81c8bb96-aa45-f2e2-0eef-c4fa4a15c6df --- callirhoe.py | 7 +- lang/DE.py | 2 + lang/EL.py | 2 + lang/EN.py | 1 + lang/FR.py | 2 + lang/TR.py | 2 + layouts/_base.py | 9 +-- layouts/bars.py | 5 +- layouts/classic.py | 5 +- layouts/sparse.py | 185 +++++++++++++++++++++++++++++++++++++++++++++ style/bw.py | 2 +- style/bw_sparse.py | 75 ++++++++++++++++++ 12 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 layouts/sparse.py create mode 100644 style/bw_sparse.py diff --git a/callirhoe.py b/callirhoe.py index 48024c8..a1ade91 100755 --- a/callirhoe.py +++ b/callirhoe.py @@ -24,12 +24,13 @@ # default values for rows/cols depending on layout (classic/bars) # fix auto-measure rendering (cairo) # fix plugin loading (without global vars) +# week markers selectable +# test layouts # allow to change background color (fill), other than white # page spec parse errors # mobile themes (e.g. 800x480) # photo support (like ImageMagick's polaroid effect) -# python source documentation # .callirhoe/config : default values for plugins (styles/layouts/lang...) and cmdline # MAYBE-TODO: @@ -181,6 +182,8 @@ if __name__ == "__main__": help="user the short version of month names (defined in language file) [%default]") parser.add_option("--long-daynames", action="store_true", default=False, help="user the long version of day names (defined in language file) [%default]") + parser.add_option("-T", "--terse-holidays", action="store_false", dest="multiday_holidays", + default=True, help="do not print holiday end markers and omit dots") for x in ["languages", "layouts", "styles", "geometries"]: add_list_option(parser, x) @@ -301,7 +304,7 @@ if __name__ == "__main__": hprovider = holiday.HolidayProvider(Style.dom, Style.dom_weekend, Style.dom_holiday, Style.dom_weekend_holiday, - Style.dom_multi, Style.dom_weekend_multi) + Style.dom_multi, Style.dom_weekend_multi, options.multiday_holidays) if options.holidays: for f in options.holidays: diff --git a/lang/DE.py b/lang/DE.py index 130a52d..4ad3923 100644 --- a/lang/DE.py +++ b/lang/DE.py @@ -32,3 +32,5 @@ long_month_name = [ '', short_month_name = [ '', u'Jan', u'Feb', u'Mrz', u'Apr', u'Mai', u'Jun', u'Jul', u'Aug', u'Sep', u'Okt', u'Nov', u'Dez' ] + +week_of_year_prefix = u'W' diff --git a/lang/EL.py b/lang/EL.py index 3c81937..8eebeb3 100644 --- a/lang/EL.py +++ b/lang/EL.py @@ -29,3 +29,5 @@ long_month_name = [ '', u'Ιανουάριος', u'Φεβρουάριος', u'Μ short_month_name = [ '', u'Ιαν', u'Φεβ', u'Μαρ', u'Απρ', u'Μαϊ', u'Ιον', u'Ιολ', u'Αυγ', u'Σεπ', u'Οκτ', u'Νοε', u'Δεκ' ] + +week_of_year_prefix = u'Ε' diff --git a/lang/EN.py b/lang/EN.py index cbf1bb1..7a671b6 100644 --- a/lang/EN.py +++ b/lang/EN.py @@ -32,3 +32,4 @@ short_month_name = [ '', u'Jan', u'Feb', u'Mar', u'Apr', u'May', u'Jun', u'Jul', u'Aug', u'Sep', u'Oct', u'Nov', u'Dec' ] +week_of_year_prefix = u'W' diff --git a/lang/FR.py b/lang/FR.py index 539ffaa..0e73290 100644 --- a/lang/FR.py +++ b/lang/FR.py @@ -30,3 +30,5 @@ long_month_name = [ '', short_month_name = [ '', u'Jan', u'Fév', u'Mar', u'Avr', u'Mai', u'Jun', u'Jul', u'Aoû', u'Sep', u'Oct', u'Nov', u'Déc' ] + +week_of_year_prefix = u'S' diff --git a/lang/TR.py b/lang/TR.py index 626ca94..77491ae 100644 --- a/lang/TR.py +++ b/lang/TR.py @@ -29,3 +29,5 @@ long_month_name = [ '', u'Ocak', u'Şubat', u'Mart', u'Nisan', u'Eylül', u'Ekim', u'Kasım', u'Aralık' ] short_month_name = long_month_name + +week_of_year_prefix = u'H' diff --git a/layouts/_base.py b/layouts/_base.py index 57d63fb..c0fe3c7 100644 --- a/layouts/_base.py +++ b/layouts/_base.py @@ -77,7 +77,7 @@ class DayCell(object): @ivar day: day of week @ivar header: header string @ivar footer: footer string - @ivar theme: (Style,Geometry,Language) tuple + @ivar theme: (Style class,Geometry class,Language module) tuple @type show_day_name: bool @ivar show_day_name: whether day name is displayed """ @@ -166,7 +166,7 @@ class CalendarRenderer(object): @ivar Year: year of first month @ivar Month: first month @ivar MonthSpan: month span - @ivar Theme: (Style,Geometry,Language) tuple + @ivar Theme: (Style module,Geometry module,Language module) tuple @ivar holiday_provider: L{HolidayProvider} object @ivar version_string: callirhoe version string @ivar options: parser options object @@ -181,14 +181,13 @@ class CalendarRenderer(object): self.version_string = version_string self.options = options - def _draw_month(self, cr, rect, month, year, daycell_thres): + def _draw_month(self, cr, rect, month, year): """this method renders a calendar month, it B{should be overridden} in any subclass @param cr: cairo context @param rect: rendering rect @param month: month @param year: year - @param daycell_thres: short/long day cell threshold """ raise NotImplementedError("base _draw_month() should be overridden") @@ -288,7 +287,7 @@ class CalendarRenderer(object): for (m,y) in p: k = len(p) - num_placed - 1 if z_order == "decreasing" else num_placed self._draw_month(page.cr, grid.item_seq(k, self.options.grid_order == "column"), - month=m, year=y, daycell_thres = self.options.short_daycell_ratio) + month=m, year=y) num_placed += 1 if (y > yy[-1]): yy.append(y) if not self.options.month_with_year and not self.options.no_footer: diff --git a/layouts/bars.py b/layouts/bars.py index c8fe838..fca35b7 100644 --- a/layouts/bars.py +++ b/layouts/bars.py @@ -30,8 +30,7 @@ parser = _base.get_parser(__name__) class CalendarRenderer(_base.CalendarRenderer): """bars layout class""" - #default thres = 2.5 - def _draw_month(self, cr, rect, month, year, daycell_thres): + def _draw_month(self, cr, rect, month, year): S,G,L = self.Theme make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot) @@ -58,7 +57,7 @@ class CalendarRenderer(_base.CalendarRenderer): day_style = holiday_tuple[2] dcell = _base.DayCell(day = (dom, day), header = holiday_tuple[0], footer = holiday_tuple[1], theme = (day_style, G.dom, L), show_day_name = True) - dcell.draw(cr, R, daycell_thres) + dcell.draw(cr, R, self.options.short_daycell_ratio) else: day_style = S.dom draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg, diff --git a/layouts/classic.py b/layouts/classic.py index dba88e1..cc5e71a 100644 --- a/layouts/classic.py +++ b/layouts/classic.py @@ -37,8 +37,7 @@ def _weekrows_of_month(year, month): class CalendarRenderer(_base.CalendarRenderer): """classic tiles layout class""" - #default thres = 2.5 - def _draw_month(self, cr, rect, month, year, daycell_thres): + def _draw_month(self, cr, rect, month, year): S,G,L = self.Theme make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot) @@ -81,7 +80,7 @@ class CalendarRenderer(_base.CalendarRenderer): day_style = holiday_tuple[2] dcell = _base.DayCell(day = (dom, col), header = holiday_tuple[0], footer = holiday_tuple[1], theme = (day_style, G.dom, L), show_day_name = False) - dcell.draw(cr, R, daycell_thres) + dcell.draw(cr, R, self.options.short_daycell_ratio) else: day_style = S.dom_weekend if col >= 5 else S.dom draw_box(cr, rect = R, stroke_rgba = day_style.frame, fill_rgba = day_style.bg, diff --git a/layouts/sparse.py b/layouts/sparse.py new file mode 100644 index 0000000..8f81e36 --- /dev/null +++ b/layouts/sparse.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +# callirhoe - high quality calendar rendering +# Copyright (C) 2012 George M. Tzoumas + +# Sparse Layout Module +# Copyright (C) 2013 Neels Hofmeyr + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +"""sparse layout""" + +from lib.xcairo import * +from lib.geom import * +import calendar +import optparse +import sys +from datetime import date, timedelta + +import _base + +def get_parser(layout_name): + """get the parser object for the layout command-line arguments + + @param layout_name: corresponding python module (.py file) + """ + lname = layout_name.split(".")[1] + parser = optparse.OptionParser(usage="%prog (...) --layout " + lname + " [options] (...)",add_help_option=False) + parser.add_option("--rows", type="int", default=1, help="force grid rows [%default]") + parser.add_option("--cols", type="int", default=0, + help="force grid columns [%default]; if ROWS and COLS are both non-zero, " + "calendar will span multiple pages as needed; if one value is zero, it " + "will be computed automatically in order to fill exactly 1 page") + parser.add_option("--grid-order", choices=["row","column"],default="row", + help="either `row' or `column' to set grid placing order row-wise or column-wise [%default]") + parser.add_option("--z-order", choices=["auto", "increasing", "decreasing"], default="auto", + help="either `increasing' or `decreasing' to set whether next month (in grid order) " + "lies above or below the previously drawn month; this affects shadow casting, " + "since rendering is always performed in increasing z-order; specifying `auto' " + "selects increasing order if and only if sloppy boxes are enabled [%default]") + parser.add_option("--month-with-year", action="store_true", default=False, + help="displays year together with month name, e.g. January 1980; suppresses year from footer line") + parser.add_option("--no-footer", action="store_true", default=False, + help="disable footer line (with year and rendered-by message)") + parser.add_option("--symmetric", action="store_true", default=False, + help="force symmetric mode (equivalent to --geom-var=month.symmetric=1). " + "In symmetric mode, day cells are equally sized and all month boxes contain " + "the same number of (possibly empty) cells, independently of how many days or " + "weeks per month. In asymmetric mode, empty rows are eliminated, by slightly " + "resizing day cells, in order to have uniform month boxes.") + parser.add_option("--padding", type="float", default=None, + help="set month box padding (equivalent to --geom-var=month.padding=PADDING)") + parser.add_option("--no-shadow", action="store_true", default=None, + help="disable box shadows") + parser.add_option("--opaque", action="store_true", default=False, + help="make background opaque (white fill)") + parser.add_option("--swap-colors", action="store_true", default=None, + help="swap month colors for even/odd years") + return parser + +parser = get_parser(__name__) + +def _draw_day_cell(cr, rect, day, header, footer, theme, show_day_name, text_height=None): + ds,G,L = theme + year, month, day_of_month, day_of_week = day + draw_box(cr, rect, ds.bg, ds.bg, mm_to_dots(ds.frame_thickness)) + + if day_of_month > 1: + x, y, w, h = rect + draw_line(cr, (x, y, w, 0), ds.frame, mm_to_dots(ds.frame_thickness)) + + if (text_height is not None) and (text_height > 0): + x, y, w, h = rect + h_diff = h - text_height + if h_diff > 0: + y += h_diff / 2 + h = text_height + rect = (x, y, w, h) + + x, y, w, h = rect + ww = h + Rleft = (x + 0.1 * h, y + 0.2 * h, ww - 0.2 * h, .6 * h) + Rmiddle = (x + h, y, ww, h) + Rmiddle_top = (x + h + 0.1 * h, y + 0.2 * h, ww, .18 * h) + bottom_h = .8 * h + Rmiddle_bottom = (x + h + 0.1 * h, y + h - bottom_h, ww, bottom_h - 0.2 * h) + #Rmiddle_top = rect_rel_scale(Rmiddle_top, .8, 0.6) + #Rmiddle_bottom = rect_rel_scale(Rmiddle, .8, 0.6) + Rright_header = (x + 2*h, y + 0.1 * h, w - 2 * ww - 0.2 * ww, 0.28 * h) + Rright_footer = (x + 2*h, y + 0.6 * h, w - 2 * ww - 0.2 * ww, 0.28 * h) + x, y, w, h = Rmiddle_bottom + hh = h + h = float(h) * 0.6 + y += float(hh) - h + Rmiddle_bottom = (x, y, w, h) + valign = 0 if show_day_name else 2 + # draw day of month (number) + draw_str(cr, text = str(day_of_month), rect = Rleft, scaling = -1, stroke_rgba = ds.fg, + align = (1,valign), font = ds.font, measure = "88") + # draw name of day + if show_day_name: + draw_str(cr, text = L.day_name[day_of_week], rect = Rmiddle_bottom, + scaling = -1, stroke_rgba = ds.fg, align = (0,valign), + font = ds.font, measure = "Mo") + # week number + if day_of_week == 0 or (day_of_month == 1 and month == 1): + week_nr = date(year, month, day_of_month).isocalendar()[1] + draw_str(cr, text = "%s%d" % (L.week_of_year_prefix, week_nr), rect = Rmiddle_top, + scaling = -1, stroke_rgba = ds.fg, align = (0,valign), + font = ds.header_font, measure = "W88") + + if header: + draw_str(cr, text = header, rect = Rright_header, scaling = -1, + stroke_rgba = ds.header, align = (1,1), font = ds.header_font, + measure='MgMgMgMgMgMgMgMgMg') + if footer: + draw_str(cr, text = footer, rect = Rright_footer, scaling = -1, + stroke_rgba = ds.footer, align = (1,1), font = ds.header_font, + measure='MgMgMgMgMgMgMgMgMg') + +class CalendarRenderer(_base.CalendarRenderer): + """sparse layout class""" + def _draw_month(self, cr, rect, month, year): + S,G,L = self.Theme + make_sloppy_rect(cr, rect, G.month.sloppy_dx, G.month.sloppy_dy, G.month.sloppy_rot) + + day, span = calendar.monthrange(year, month) + wmeasure = 'A'*max(map(len,L.day_name)) + mmeasure = 'A'*max(map(len,L.month_name)) + + rows = 31 if G.month.symmetric else span + grid = VLayout(rect_from_origin(rect), 32) # title bar always symmetric + dom_grid = VLayout(grid.item_span(31,1), rows) + + # determine text height + tmp_grid = VLayout(grid.item_span(31,1), 31) + text_height = tmp_grid.item(0)[3] + + # draw box shadow + if S.month.box_shadow: + f = S.month.box_shadow_size + shad = (f,-f) if G.landscape else (f,f) + draw_shadow(cr, rect_from_origin(rect), shad) + + # draw day cells + for dom in range(1,span+1): + R = dom_grid.item(dom-1) + holiday_tuple = self.holiday_provider(year, month, dom, day) + day_style = holiday_tuple[2] + header = holiday_tuple[0] + footer = holiday_tuple[1] + if footer: + if header: + header = "%s, %s" % (header, footer) + else: + header = footer + _draw_day_cell(cr, rect = R, day = (year, month, dom, day), + header = header, footer = None, + theme = (day_style, G.dom, L), show_day_name = True, + text_height = text_height) + + day = (day + 1) % 7 + + # draw month title (name) + mcolor = S.month.color_map_bg[year%2][month] + mcolor_fg = S.month.color_map_fg[year%2][month] + R_mb = grid.item(0) + R_text = rect_rel_scale(R_mb, 1, 0.5) + mshad = None + if S.month.text_shadow: + f = S.month.text_shadow_size + mshad = (f,-f) if G.landscape else (f,f) + draw_str(cr, text = L.month_name[month], rect = R_text, scaling = -1, stroke_rgba = mcolor_fg, + align = (2,0), font = S.month.font, measure = mmeasure, shadow = mshad) + cr.restore() diff --git a/style/bw.py b/style/bw.py index db61a82..5a4a8b2 100644 --- a/style/bw.py +++ b/style/bw.py @@ -16,7 +16,7 @@ # --- style.bw --- -"""module defining the black-& white style""" +"""module defining the black & white style""" class dow: """day of week style""" diff --git a/style/bw_sparse.py b/style/bw_sparse.py new file mode 100644 index 0000000..5456754 --- /dev/null +++ b/style/bw_sparse.py @@ -0,0 +1,75 @@ +# callirhoe - high quality calendar rendering +# Copyright (C) 2012 George M. Tzoumas + +# Sparse Style Definition +# Copyright (C) 2013 Neels Hofmeyr + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ + +# --- style.bw --- + +"""module defining the black & white sparse style + +to be used with sparse layout +""" + +class dow: + """day of week style""" + fg = (0,0,0) + frame_thickness = 0.1 + frame = (.5, .5, .5) + font = "Arial" + +class dom: + """day of month style""" + bg = (1,1,1) + frame = (.9, .9, .9) + frame_thickness = 0.1 + fg = (0.3,0.3,0.3) + font = "Times New Roman" + header = (0.3,0.3,0.3) + footer = header + header_font = footer_font = "Arial" + +class dom_holiday(dom): + """day of month (holiday, indicated by the OFF flag in the holiday file)""" + bg = (0.95,0.95,0.95) + +class dom_weekend(dom_holiday): + """day of month style (weekend)""" + font = ("Times New Roman", 0, 1) + +class dom_weekend_holiday(dom_weekend): + """day of month (weekend & holiday)""" + pass + +class dom_multi(dom_holiday): + """day of month (multi-day holiday)""" + pass + +class dom_weekend_multi(dom_weekend_holiday): + """day of month (weekend in multi-day holiday)""" + pass + +class month: + """month style""" + font = ("Times New Roman", 0, 1) + frame = (0,0,0) + frame_thickness = 0.2 + bg = (1,1,1) + color_map = ((1,1,1),)*13 + color_map_bg = (((1,1,1),)*13,((.8,.8,.8),)*13) + color_map_fg = (((0,0,0),)*13,((0,0,0),)*13) + box_shadow = False + text_shadow = False