KivyCalendar font size for Day Labels

230 views
Skip to first unread message

Shoumik Das

unread,
May 24, 2020, 1:27:07 AM5/24/20
to Kivy users support
Hi, I downloaded the source code for KivyCalendar from the following URL: https://bitbucket.org/xxblx/kivycalendar/src/master/

I integrated it with my app and it is now working fine when the window size is big. Please see the screenshot below.


However, if I resize the window to make it narrow enough to simulate a mobile screen, the text of the day labels wrap and flow over to the next line giving a messy look.


I sort of found a work-around by reducing the font_size in the calendar_ui.py file so as to prevent the text-wrap but this reduces the readability when the window is large.

<DayAbbrLabel>:
    text_size: self.size[0], None
    halign: "center"
    font_size: '11sp'

Now this is what I get:


Is there a way to automatically adjust the font_size when the window is resized? Also, on the disabled dates, some strokes are visible which are probably the bounds of the button widget. How can I remove this? Please advise.

Code for calendar_ui.py:

#!/usr/bin/python
# -*- coding: utf-8 -*-

###########################################################
# KivyCalendar (X11/MIT License)
# Calendar & Date picker widgets for Kivy (http://kivy.org)
# https://bitbucket.org/xxblx/kivycalendar
#
# Oleg Kozlov (xxblx), 2015
# https://xxblx.bitbucket.org/
###########################################################
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.properties import NumericProperty, ReferenceListProperty

import calendar_data as cal_data
###########################################################
Builder.load_string("""
<ArrowButton>:
    background_normal: ""
    background_down: ""
    background_color: 1, 1, 1, 0
    size_hint: .1, .1

<MonthYearLabel>:
    pos_hint: {"
top": 1, "center_x": .5}
    size_hint: None, 0.1
    halign: "
center"

<MonthsManager>:
    pos_hint: {"
top": .9}
    size_hint: 1, .9

<ButtonsGrid>:
    cols: 7
    rows: 7
    size_hint: 1, 1
    pos_hint: {"
top": 1}

<DayAbbrLabel>:
    text_size: self.size[0], None
    halign: "
center"
    valign: "
middle"
    font_size: '11sp'

<DayAbbrWeekendLabel>:
    color: 1, 0, 0, 1
   
<DayButton>:
    group: "
day_num"
   
<DayNumWeekendButton>:
    background_color: 1, 0, 0, 1
"""
)    
###########################################################

class DatePicker(TextInput):
   
"""
    Date picker is a textinput, if it focused shows popup with calendar
    which allows you to define the popup dimensions using pHint_x, pHint_y,
    and the pHint lists, for example in kv:
    DatePicker:
        pHint: 0.7,0.4
    would result in a size_hint of 0.7,0.4 being used to create the popup
    """

    pHint_x
= NumericProperty(0.0)
    pHint_y
= NumericProperty(0.0)
    pHint
= ReferenceListProperty(pHint_x ,pHint_y)

   
def __init__(self, touch_switch=False, *args, **kwargs):
       
super(DatePicker, self).__init__(*args, **kwargs)
       
       
self.touch_switch = touch_switch
       
self.init_ui()
       
   
def init_ui(self):
       
       
self.text = cal_data.today_date()
       
# Calendar
       
self.cal = CalendarWidget(as_popup=True,
                                  touch_switch
=self.touch_switch)
       
# Popup
       
self.popup = Popup(content=self.cal, on_dismiss=self.update_value,
                           title
="")
       
self.cal.parent_popup = self.popup
       
       
self.bind(focus=self.show_popup)
       
   
def show_popup(self, isnt, val):
       
"""
        Open popup if textinput focused,
        and regardless update the popup size_hint
        """

       
self.popup.size_hint=self.pHint        
       
if val:
           
# Automatically dismiss the keyboard
           
# that results from the textInput
           
Window.release_all_keyboards()
           
self.popup.open()
       
   
def update_value(self, inst):
       
""" Update textinput value on popup close """
           
       
self.text = "%s.%s.%s" % tuple(self.cal.active_date)
       
self.focus = False

class CalendarWidget(RelativeLayout):
   
""" Basic calendar widget """
   
   
def __init__(self, as_popup=False, touch_switch=False, *args, **kwargs):
       
super(CalendarWidget, self).__init__(*args, **kwargs)
       
       
self.as_popup = as_popup
       
self.touch_switch = touch_switch
       
self.prepare_data()    
       
self.init_ui()
       
   
def init_ui(self):
       
       
self.left_arrow = ArrowButton(text="<", on_press=self.go_prev,
                                      pos_hint
={"top": 1, "left": 0})
       
       
self.right_arrow = ArrowButton(text=">", on_press=self.go_next,
                                       pos_hint
={"top": 1, "right": 1})
       
       
self.add_widget(self.left_arrow)        
       
self.add_widget(self.right_arrow)
       
       
# Title        
       
self.title_label = MonthYearLabel(text=self.title)
       
self.add_widget(self.title_label)
       
       
# ScreenManager
       
self.sm = MonthsManager()
       
self.add_widget(self.sm)
       
       
self.create_month_scr(self.quarter[1], toogle_today=True)
   
   
def create_month_scr(self, month, toogle_today=False):
       
""" Screen with calendar for one month """        
       
        scr
= Screen()
        m
= self.month_names_eng[self.active_date[1] - 1]
        scr
.name = "%s-%s" % (m, self.active_date[2])  # like march-2015
       
       
# Grid for days
        grid_layout
= ButtonsGrid()
        scr
.add_widget(grid_layout)
       
       
# Days abbrs
       
for i in range(7):
           
if i >= 5:  # weekends
                l
= DayAbbrWeekendLabel(text=self.days_abrs[i])
           
else:  # work days
                l
= DayAbbrLabel(text=self.days_abrs[i])
           
            grid_layout
.add_widget(l)
           
       
# Buttons with days numbers
       
for week in month:
           
for day in week:
               
if day[1] >= 5:  # weekends
                    tbtn
= DayNumWeekendButton(text=str(day[0]))
               
else:  # work days
                    tbtn
= DayNumButton(text=str(day[0]))
               
                tbtn
.bind(on_press=self.get_btn_value)
               
               
if toogle_today:
                   
# Down today button
                   
if day[0] == self.active_date[0] and day[2] == 1:
                        tbtn
.state = "down"
               
# Disable buttons with days from other months
               
if day[2] == 0:
                    tbtn
.disabled = True
               
                grid_layout
.add_widget(tbtn)

       
self.sm.add_widget(scr)
       
   
def prepare_data(self):
       
""" Prepare data for showing on widget loading """
   
       
# Get days abbrs and month names lists
       
self.month_names = cal_data.get_month_names()
       
self.month_names_eng = cal_data.get_month_names_eng()
       
self.days_abrs = cal_data.get_days_abbrs()    
       
       
# Today date
       
self.active_date = cal_data.today_date_list()
       
# Set title
       
self.title = "%s - %s" % (self.month_names[self.active_date[1] - 1],
                                 
self.active_date[2])
               
       
# Quarter where current month in the self.quarter[1]
       
self.get_quarter()
   
   
def get_quarter(self):
       
""" Get calendar and months/years nums for quarter """
       
       
self.quarter_nums = cal_data.calc_quarter(self.active_date[2],
                                                 
self.active_date[1])
       
self.quarter = cal_data.get_quarter(self.active_date[2],
                                           
self.active_date[1])
   
   
def get_btn_value(self, inst):
       
""" Get day value from pressed button """
       
       
self.active_date[0] = int(inst.text)
               
       
if self.as_popup:
           
self.parent_popup.dismiss()
       
   
def go_prev(self, inst):
       
""" Go to screen with previous month """        

       
# Change active date
       
self.active_date = [self.active_date[0], self.quarter_nums[0][1],
                           
self.quarter_nums[0][0]]

       
# Name of prev screen
        n
= self.quarter_nums[0][1] - 1
        prev_scr_name
= "%s-%s" % (self.month_names_eng[n],
                                   
self.quarter_nums[0][0])
       
       
# If it's doen't exitst, create it
       
if not self.sm.has_screen(prev_scr_name):
           
self.create_month_scr(self.quarter[0])
           
       
self.sm.current = prev_scr_name
       
self.sm.transition.direction = "left"
       
       
self.get_quarter()
       
self.title = "%s - %s" % (self.month_names[self.active_date[1] - 1],
                                 
self.active_date[2])
       
       
self.title_label.text = self.title
   
   
def go_next(self, inst):
       
""" Go to screen with next month """
       
         
# Change active date
       
self.active_date = [self.active_date[0], self.quarter_nums[2][1],
                           
self.quarter_nums[2][0]]

       
# Name of prev screen
        n
= self.quarter_nums[2][1] - 1
        next_scr_name
= "%s-%s" % (self.month_names_eng[n],
                                   
self.quarter_nums[2][0])
       
       
# If it's doen't exitst, create it
       
if not self.sm.has_screen(next_scr_name):
           
self.create_month_scr(self.quarter[2])
           
       
self.sm.current = next_scr_name
       
self.sm.transition.direction = "right"
       
       
self.get_quarter()
       
self.title = "%s - %s" % (self.month_names[self.active_date[1] - 1],
                                 
self.active_date[2])
       
       
self.title_label.text = self.title
       
   
def on_touch_move(self, touch):
       
""" Switch months pages by touch move """
               
       
if self.touch_switch:
           
# Left - prev
           
if touch.dpos[0] < -30:
               
self.go_prev(None)
           
# Right - next
           
elif touch.dpos[0] > 30:
               
self.go_next(None)
       
class ArrowButton(Button):
   
pass

class MonthYearLabel(Label):
   
pass

class MonthsManager(ScreenManager):
   
pass

class ButtonsGrid(GridLayout):
   
pass

class DayAbbrLabel(Label):
   
pass

class DayAbbrWeekendLabel(DayAbbrLabel):
   
pass

class DayButton(ToggleButton):
   
pass

class DayNumButton(DayButton):
   
pass

class DayNumWeekendButton(DayButton):
   
pass


Thanks in advance


Elliot Garbus

unread,
May 24, 2020, 8:58:14 AM5/24/20
to kivy-...@googlegroups.com

The lines in the disabled buttons are the button boarder.  Because the button is so small there is overlap.  Reduce the size of the border.

https://kivy.org/doc/stable/api-kivy.uix.button.html?highlight=button#kivy.uix.button.Button.border

 

I’d recommend picking 2 font sizes that work. (small, large).  If the width (of the label or popup or…) is greater than some threshold value, use large, else small.

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/07154fd9-08d1-4c4b-9e20-444f697fcea7%40googlegroups.com.

 

Shoumik Das

unread,
May 24, 2020, 2:20:58 PM5/24/20
to Kivy users support
Hi Elliot,

That worked. I reduced the border size to "border: 5, 5, 5, 5" in the button widget and it did the trick. Thank you very much for the suggestion. I really like the idea of using 2 font sizes based on the width of the label. However, I sort of fixed that issue by increasing the space allotted to the popup calendar widget so that the day labels do not wrap when the window size is reduced.

However, using two font sizes is the recommended approach I would try in any future implementation. This is what it looks now in a reduced window (as per expectation):


I was fiddling around with the default format of the selected date in the text input field.


I want to display it in the format: dd-mon-yy. Eg: 24-May-20

So I looked up the source code and made the following modification in calendar_data.py:

def today_date():
   
""" Return today date dd.mm.yyyy like 28.02.2015 """

   
return datetime.now().strftime("%d.%m.%Y")

I changed the return format to:
return datetime.now().strftime("%d-%b-%y")

This works fine when I load the calendar widget for the first time. If I select a new date, the format gets over-ridden to dd.mm.yyyy.

After new selection:

I did some look up in the code and found that the following function in the calendar_data.py was returning a tuple for calculations and display:

def today_date_list():
   
""" Return list with today date """
   
   
return [datetime.now().day, datetime.now().month, datetime.now().year]

If I use datetime.now().strftime("%b") in place of datetime.now().month in the return tuple, I get a data type mismatch error because I am returning string and integer in the same tuple. The active_date variable receives this tuple but is unable to format it correctly for display.

Inside the calendar_ui.py file, this is what sets the new selection:

def update_value(self, inst):
       
""" Update textinput value on popup close """

           
       
self.text = "%s-%s-%s" % tuple(self.cal.active_date)
       
self.focus = False

cal is an instance of the CalendarWidget class.

This is where the text content of the text input field is getting set:

def prepare_data(self):
       
""" Prepare data for showing on widget loading """
   
       
# Get days abbrs and month names lists
       
self.month_names = cal_data.get_month_names()
       
self.month_names_eng = cal_data.get_month_names_eng()
       
self.days_abrs = cal_data.get_days_abbrs()    
       
       
# Today date
       
self.active_date = cal_data.today_date_list()
       
# Set title
       
self.title = "%s - %s" % (self.month_names[self.active_date[1] - 1],
                                 
self.active_date[2])
               
       
# Quarter where current month in the self.quarter[1]
       
self.get_quarter()

The data type mismatch error is encounetered the set title step:

# Set title
       
self.title = "%s - %s" % (self.month_names[self.active_date[1] - 1],
                                 
self.active_date[2])

Maybe I am not able to format the tuple correctly for display. Can you please advise how I may update the title field in the 25-May-20 format? I didn't try to change the return format of the tuple as it is being used in calculations throughout the code.

Thanks in advance

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-...@googlegroups.com.

Elliot Garbus

unread,
May 24, 2020, 3:50:37 PM5/24/20
to kivy-...@googlegroups.com

Looking good!

It looks like there are 2 questions,

  1. how to modify update_value() to change the format of the returned string.
  2. How to change the title format

 

 

modify update_value() to change the format of the returned string:

I would limit the changes to the update_value() method, reducing the possibility of propagating errors.

 

def update_value(self, inst):
        """ Update textinput value on popup close """
           
        self.text = "%s-%s-%s" % tuple(self.cal.active_date)
        self.focus = False

 

I’ll assume that self.cal.active_date holds 3 ints.

               day, month, year = tuple(self.cal.active_date)

 

you need to convert the month 1-12 into the name of the month.  There is a method in TimeEncoding that returns a list of month names.  Use month – 1 to index into the returned list.  That will give you an month name. Alternatively you could create a dict to return the month name.

 

Convert the year in to a string and take the last 2 characters.

 

year_2 = str(year)[2:4]

 

then you can use an fstring or a format string to assemble the elements as you like.

 

How to change the title format

 

It looks like the title needs to changed in prepare_data, go_prev, and go_next. 

I would use an fstring.

 

year_2 = str(self.active_date[2])[2:4]

self.title = f’{self.active_data[0]}-{self.month_names[self.active_date[1] - 1]}-{year_2}’)

However, if I resize the window to make it narrow enough to simulate a mobile screen, the text of the day labels wrap and flow over to the next line giving a messy look.

 

 

I sort of found a work-around by reducing the font_size in the calendar_ui.py file so as to prevent the text-wrap but this reduces the readability when the window is large.

 

<DayAbbrLabel>:
    text_size: self.size[0], None
    halign: "center"
    font_size: '11sp'

 

Now this is what I get:

 

 

Is there a way to automatically adjust the font_size when the window is resized? Also, on the disabled dates, some strokes are visible which are probably the bounds of the button widget. How can I remove this? Please advise.

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/5bcf1143-86d7-4466-a5f5-c775c76975b0%40googlegroups.com.

 

Shoumik Das

unread,
May 25, 2020, 4:48:52 AM5/25/20
to Kivy users support
Hi Eliott,

I followed your advice and made the following changes:

calendar_data.py

Imported month_abbr.

from calendar import month_name, day_abbr, Calendar, monthrange, month_abbr

Updated today_date()

def today_date():
   
""" Return today date dd.mm.yyyy like 28.02.2015 """

#   return datetime.now().strftime("%d.%m.%Y")
   
return datetime.now().strftime("%d-%b-%y")

calendar_ui.py

def update_value(self, inst):
       
""" Update textinput value on popup close """


        sday
, smonth, syear = tuple(self.cal.active_date)
       
self.text = str(sday)+"-"+cal_data.month_abbr[smonth]+"-"+str(syear)[2:4]
       
# self.text = "%s-%s-%s" % tuple(self.cal.active_date)
       
self.focus = False

Updated the kv code as follows for centering the date horizontally and vertically in the textinput field:

<DatePicker>:
    padding: [(self.width-self._get_text_width(self.text,self.tab_width,self._label_cached))/2, (self.height-self.line_height)/2]

So far, the behaviour seems to be as expected. Thanks a lot for your help. However, I do have a question regarding the title format. I have not made any change in the prepare_data, go_prev, and go_next methods that you had suggested. What do these do? When I open the calendar window, I see only the Month name and Year. E.g: May - 2020

Is this what you are referring to?


However, I have noticed something else. Let's say I selected 26-May-20 as the date. This is the display I get:


If I reopen the calendar, the date shows as properly selected.


Now, If go to the next month, i.e. June. 2020 and do not select a date but simply dismiss the pop-up window, the date automatically gets changed to 26-Jun-20.


Looks like the date automatically got updated even though I did not select any date from the month of June. Is this normal behaviour of this calendar module? Or is it happening because I did not update the go_prev and go_next methods? Please advise. I would prefer the date to not get changed automatically if I do not click on a date button.

Thanks as usual for your valuable advice.

Elliot Garbus

unread,
May 25, 2020, 9:12:18 AM5/25/20
to kivy-...@googlegroups.com

 

Looks like the date automatically got updated even though I did not select any date from the month of June. Is this normal behaviour of this calendar module? Or is it happening because I did not update the go_prev and go_next methods? Please advise. I would prefer the date to not get changed automatically if I do not click on a date button.

 

Reading the code, if you don’t want the data to change when you change months, but don’t select a day button you will need to modify:

The methods: go_prev, go_next, and get_btn_value.  It looks like you will need to create new instance variables to hold the current month/year/qtr.  You will want these changed in go_prev/go_next.  The method get_btn_value only updates the day, you will need to have get_btn_value() be the only place that active_date is set.

I was fiddling around with the default format of the selected date in the text input field.

 

 

I want to display it in the format: dd-mon-yy. Eg: 24-May-20

 

So I looked up the source code and made the following modification in calendar_data.py:

 

def today_date():
    """ Return today date dd.mm.yyyy like 28.02.2015 """

    return datetime.now().strftime("%d.%m.%Y")


I changed the return format to:

return datetime.now().strftime("%d-%b-%y")


This works fine when I load the calendar widget for the first time. If I select a new date, the format gets over-ridden to dd.mm.yyyy.

 

After new selection:

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/e3c5bd66-1bce-473c-961f-27a5c57bd184%40googlegroups.com.

 

Shoumik Das

unread,
May 25, 2020, 10:47:15 AM5/25/20
to Kivy users support
Hi Eliott, thanks for helping me understand. I think I will try not to mess around with the calendar code too much for the time being. The existing functionality should serve the purpose. I will experiment with this at a later time. I am assuming that the go_prev, go_next and prepare_data methods do not need any further modification to retain the display of the dd-mon-yy format.

Thank you very much for the detailed explanation.
Reply all
Reply to author
Forward
0 new messages