Source code for pympress.talk_time

# -*- coding: utf-8 -*-
#
#       talk_time.py
#
#       Copyright 2017 Cimbali <me@cimba.li>
#
#       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 2 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, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.
"""
:mod:`pympress.talk_time` -- Manages the clock of elapsed talk time
-------------------------------------------------------------------
"""

import logging
logger = logging.getLogger(__name__)

import time

import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib


[docs] class TimeLabelColorer(object): """ Manage the colors of a label with a set of colors between which to fade, based on how much time remains. Times are given in seconds (<0 has run out of time). In between timestamps the color will interpolated linearly, outside of the intervals the closest color will be used. Args: label_time (:class:`Gtk.Label`): the label where the talk time is displayed """ #: The :class:`Gtk.Label` whose colors need updating label_time = None #: :class:`~Gdk.RGBA` The default color of the info labels label_color_default = None #: :class:`~Gtk.CssProvider` affecting the style context of the labels color_override = None #: `list` of tuples (`int`, :class:`~Gdk.RGBA`), which are the desired colors at the corresponding timestamps. #: Sorted on the timestamps. color_map = [] def __init__(self, label_time): self.label_time = label_time style_context = self.label_time.get_style_context() self.color_override = Gtk.CssProvider() style_context.add_provider(self.color_override, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1) self.label_color_default = self.load_color_from_css(style_context) label_color_ett_reached = self.load_color_from_css(style_context, "ett-reached") label_color_ett_info = self.load_color_from_css(style_context, "ett-info") label_color_ett_warn = self.load_color_from_css(style_context, "ett-warn") self.color_map = [ ( 300, self.label_color_default), ( 0, label_color_ett_reached), (-150, label_color_ett_info), (-300, label_color_ett_warn) ]
[docs] def load_color_from_css(self, style_context, class_name = None): """ Add class class_name to the time label and return its color. Args: label_time (:class:`Gtk.Label`): the label where the talk time is displayed style_context (:class:`~Gtk.StyleContext`): the CSS context managing the color of the label class_name (`str` or `None`): The name of the class, if any Returns: :class:`~Gdk.RGBA`: The color of the label with class "class_name" """ if class_name: style_context.add_class(class_name) self.label_time.show() color = style_context.get_color(Gtk.StateType.NORMAL) if class_name: style_context.remove_class(class_name) return color
[docs] def default_color(self): """ Forces to reset the default colors on the label. """ self.color_override.load_from_data(''.encode('ascii'))
[docs] def update_time_color(self, remaining): """ Update the color of the time label based on how much time is remaining. Args: remaining (`int`): Remaining time until estimated talk time is reached, in seconds. """ if (remaining <= 0 and remaining > -5) or (remaining <= -300 and remaining > -310): self.label_time.get_style_context().add_class("time-warn") else: self.label_time.get_style_context().remove_class("time-warn") prev_time, prev_color = None, None for timestamp, color in self.color_map: if remaining >= timestamp: break prev_time, prev_color = (timestamp, color) else: # if remaining < all timestamps, use only last color prev_color = None if prev_color: position = (remaining - prev_time) / (timestamp - prev_time) color_spec = '* {{color: mix({}, {}, {})}}'.format(prev_color.to_string(), color.to_string(), position) else: color_spec = '* {{color: {}}}'.format(color.to_string()) self.color_override.load_from_data(color_spec.encode('ascii'))
[docs] class TimeCounter(object): """ A double counter, that displays the time elapsed in the talk and a clock. Args: builder (builder.Builder): The builder from which to load widgets. ett (`int`): the estimated time for the talk, in seconds. timing_tracker: (:class:`~pympress.extras.TimingReport`): to inform when the slides change autoplay: (:class:`~pympress.dialog.AutoPlay`): to adjust the timer display if we’re auto-playing/looping slides """ #: Elapsed time :class:`~Gtk.Label` label_time = None #: Clock :class:`~Gtk.Label` label_clock = None #: Time at which the counter was started, `int` in seconds as returned by :func:`~time.time()` restart_time = 0 #: Time elapsed since the beginning of the presentation, `int` in seconds elapsed_time = 0 #: Timer paused status, `bool` paused = True #: :class:`~TimeLabelColorer` that handles setting the colors of :attr:`label_time` label_colorer = None #: :class:`~pympress.editable_label.EstimatedTalkTime` that handles changing the ett ett = None #: The pause-timer :class:`~Gio.Action` pause_action = None #: The :class:`~pympress.extras.TimingReport`, needs to know when the slides change timing_tracker = None #: The :class:`~pympress.dialog.AutoPlay`, to adjust the timer display if we’re auto-playing/looping slides autoplay = None def __init__(self, builder, ett, timing_tracker, autoplay): super(TimeCounter, self).__init__() self.label_colorer = TimeLabelColorer(builder.get_object('label_time')) self.ett = ett self.timing_tracker = timing_tracker self.autoplay = autoplay builder.load_widgets(self) builder.setup_actions({ 'pause-timer': dict(activate=self.switch_pause, state=self.paused), 'reset-timer': dict(activate=self.reset_timer), }) self.pause_action = builder.get_application().lookup_action('pause-timer') # Setup timer for clocks GLib.timeout_add(250, self.update_time)
[docs] def switch_pause(self, gaction, param=None): """ Switch the timer between paused mode and running (normal) mode. Returns: `bool`: whether the clock's pause was toggled. """ if self.paused: self.unpause() else: self.pause() return None
[docs] def pause(self): """ Pause the timer if it is not paused, otherwise do nothing. Returns: `bool`: whether the clock's pause was toggled. """ if self.paused: return False self.paused = True self.pause_action.change_state(GLib.Variant.new_boolean(self.paused)) self.elapsed_time += time.time() - self.restart_time self.timing_tracker.end_time = self.elapsed_time if self.autoplay.is_looping(): self.autoplay.pause() self.update_time() return True
[docs] def unpause(self): """ Unpause the timer if it is paused, otherwise do nothing. Returns: `bool`: whether the clock's pause was toggled. """ if not self.paused: return False self.restart_time = time.time() self.paused = False self.pause_action.change_state(GLib.Variant.new_boolean(self.paused)) if self.autoplay.is_looping(): self.autoplay.unpause() self.update_time() return True
[docs] def reset_timer(self, *args): """ Reset the timer. """ self.timing_tracker.reset(self.current_time()) self.restart_time = time.time() self.elapsed_time = 0 if self.autoplay.is_looping(): self.autoplay.start_looping() self.update_time()
[docs] def current_time(self): """ Returns the time elapsed in the presentation. Returns: `int`: the time since the presentation started in seconds. """ # Time elapsed since the beginning of the presentation if self.paused: return self.elapsed_time else: return self.elapsed_time + (time.time() - self.restart_time)
[docs] def update_time(self): """ Update the timer and clock labels. Returns: `bool`: `True` (to prevent the timer from stopping) """ # Current time clock = time.strftime('%X') # '%H:%M:%S' elapsed = self.current_time() # Time elapsed since the beginning of the presentation if self.autoplay.is_looping(): first, stop, loop, delay = self.autoplay.get_page_range() display_time = '{} {}-{} / {:.1f}s'.format(_('Loop') if loop else _('Auto'), first + 1, stop, delay / 1000) else: display_time = '{:02}:{:02}'.format(*divmod(int(elapsed), 60)) if self.paused: display_time += ' ' + _('(paused)') self.label_time.set_text(display_time) self.label_clock.set_text(clock) if not self.paused: self.timing_tracker.end_time = elapsed if self.ett.est_time: self.label_colorer.update_time_color(self.ett.est_time - elapsed) else: self.label_colorer.default_color() return True