From 4bbd16182dab69da9ca7ad13309962af40529469 Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Tue, 12 Jun 2012 17:49:56 +0200 Subject: add timer-applet --- .../src/timerapplet/core/AppletMateConfWrapper.py | 89 +++++++++++ timer-applet/src/timerapplet/core/Makefile.am | 6 + timer-applet/src/timerapplet/core/PresetsStore.py | 160 +++++++++++++++++++ timer-applet/src/timerapplet/core/Timer.py | 175 +++++++++++++++++++++ timer-applet/src/timerapplet/core/__init__.py | 19 +++ 5 files changed, 449 insertions(+) create mode 100644 timer-applet/src/timerapplet/core/AppletMateConfWrapper.py create mode 100644 timer-applet/src/timerapplet/core/Makefile.am create mode 100644 timer-applet/src/timerapplet/core/PresetsStore.py create mode 100644 timer-applet/src/timerapplet/core/Timer.py create mode 100644 timer-applet/src/timerapplet/core/__init__.py (limited to 'timer-applet/src/timerapplet/core') diff --git a/timer-applet/src/timerapplet/core/AppletMateConfWrapper.py b/timer-applet/src/timerapplet/core/AppletMateConfWrapper.py new file mode 100644 index 00000000..c5d09619 --- /dev/null +++ b/timer-applet/src/timerapplet/core/AppletMateConfWrapper.py @@ -0,0 +1,89 @@ +# Copyright (C) 2008 Jimmy Do +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from os import path +import mateconf + +class AppletMateConfWrapper(object): + def __init__(self, applet, schema_path, standalone_key): + object.__init__(self) + self._connection_ids = [] + + self._client = mateconf.client_get_default() + + # Get preferences key path for the given applet instance. + self._base_path = applet.get_preferences_key() + if self._base_path is not None: + # Apply the schema to the applet instance preferences key. + applet.add_preferences(schema_path) + else: + # NOTE: Don't need to apply schema here because the Timer Applet schema file + # already specifies that the schema be automatically applied to the standalone key. + + self._base_path = standalone_key + + # Applet would usually do this for us, but since we're running in standalone mode, + # we have to do this ourselves in order to receive MateConf change notifications. + self._client.add_dir(self._base_path, mateconf.CLIENT_PRELOAD_RECURSIVE) + + print 'Base prefs path = %s' % self._base_path + + def get_base_path(self): + return self._base_path + + def add_notification(self, relative_key, callback, data=None): + """Register for notifications of changes to the given preference key. + + relative_key should be relative to the applet's base preferences key path. + callback should look like: callback(MateConfValue, data=None) + """ + connection_id = self._client.notify_add(self._get_full_path(relative_key), + self._notification_callback, (callback, data)) + self._connection_ids.append(connection_id) + + def get_string(self, relative_key): + return self._client.get_string(self._get_full_path(relative_key)) + + def get_bool(self, relative_key): + return self._client.get_bool(self._get_full_path(relative_key)) + + def set_string(self, relative_key, val): + self._client.set_string(self._get_full_path(relative_key), val) + + def set_bool(self, relative_key, val): + self._client.set_bool(self._get_full_path(relative_key), val) + + def delete(self): + for connection_id in self._connection_ids: + self._client.notify_remove(connection_id) + self._connection_ids = [] + + def _notification_callback(self, client, cnxn_id, entry, data=None): + (callback, real_data) = data + + # mateconf_value is of type MateConfValue (mateconf.Value) + mateconf_value = entry.get_value() + + # Ignore when mateconf_value is None because that + # means that the settings are being removed + # because the applet has been removed from + # the panel. + if mateconf_value != None: + callback(mateconf_value, real_data) + + def _get_full_path(self, relative_key): + return path.join(self._base_path, relative_key) + diff --git a/timer-applet/src/timerapplet/core/Makefile.am b/timer-applet/src/timerapplet/core/Makefile.am new file mode 100644 index 00000000..6c974866 --- /dev/null +++ b/timer-applet/src/timerapplet/core/Makefile.am @@ -0,0 +1,6 @@ +moduledir = $(pythondir)/timerapplet/core +module_PYTHON = \ + __init__.py \ + AppletMateConfWrapper.py \ + PresetsStore.py \ + Timer.py diff --git a/timer-applet/src/timerapplet/core/PresetsStore.py b/timer-applet/src/timerapplet/core/PresetsStore.py new file mode 100644 index 00000000..edd5655a --- /dev/null +++ b/timer-applet/src/timerapplet/core/PresetsStore.py @@ -0,0 +1,160 @@ +# Copyright (C) 2008 Jimmy Do +# Copyright (C) 2010 Kenny Meyer +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +try: + from xml.etree import ElementTree as et +except: + from elementtree import ElementTree as et + +import os +from os import path +import gobject +import gtk +import timerapplet.utils as utils + +from timerapplet.utils import (serialize_bool, + deserialize_bool, + seconds_to_hms, + hms_to_seconds) +from timerapplet.defs import VERSION + +class PersistentStore(gtk.ListStore): + def __init__(self, load_func, save_func, *args): + gtk.ListStore.__init__(self, *args) + load_func(self) + + self.connect('row-deleted', lambda model, row_path: save_func(self)) + self.connect('row-changed', lambda model, row_path, row_iter: save_func(self)) + +class PresetsStore(gobject.GObject): + (_NAME_COL, + _HOURS_COL, + _MINUTES_COL, + _SECONDS_COL, + _COM_COL, + _NEXT_COL, + _AUTO_START_COL) = xrange(7) + + def __init__(self, filename): + object.__init__(self) + self._model = PersistentStore(lambda model: PresetsStore._load_presets(model, filename), + lambda model: PresetsStore._save_presets(model, filename), + gobject.TYPE_STRING, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + ) + + def get_model(self): + """Return GtkTreeModel. + + Should not rely on it being any particular subtype of GtkTreeModel. + + """ + return self._model + + def get_preset(self, row_iter): + return self._model.get(row_iter, + PresetsStore._NAME_COL, + PresetsStore._HOURS_COL, + PresetsStore._MINUTES_COL, + PresetsStore._SECONDS_COL, + PresetsStore._COM_COL, + PresetsStore._NEXT_COL, + PresetsStore._AUTO_START_COL, + ) + + def add_preset(self, name, hours, minutes, seconds, command, next_timer, + auto_start): + self._model.append((name, hours, minutes, seconds, command, next_timer, + auto_start)) + + def modify_preset(self, row_iter, name, hours, minutes, seconds, command, + next_timer, auto_start): + self._model.set(row_iter, + PresetsStore._NAME_COL, name, + PresetsStore._HOURS_COL, hours, + PresetsStore._MINUTES_COL, minutes, + PresetsStore._SECONDS_COL, seconds, + PresetsStore._COM_COL, command, + PresetsStore._NEXT_COL, next_timer, + PresetsStore._AUTO_START_COL, auto_start + ) + + def remove_preset(self, row_iter): + self._model.remove(row_iter) + + def preset_name_exists_case_insensitive(self, preset_name): + preset_name = preset_name.lower() + for preset in self._model: + if preset_name == preset[PresetsStore._NAME_COL].lower(): + return True + return False + + def _load_presets(model, file_path): + try: + tree = et.parse(file_path) + except: + return + + root = tree.getroot() + + for node in root: + name = node.get('name') + (hours, minutes, seconds) = seconds_to_hms(int(node.get('duration'))) + command = node.get('command') + next_timer = node.get('next_timer') + auto_start = node.get('auto_start') + model.append((name, hours, minutes, seconds, command, next_timer, + deserialize_bool(auto_start))) + _load_presets = staticmethod(_load_presets) + + def _save_presets(model, file_path): + root = et.Element('timerapplet') + root.set('version', VERSION) + + def add_xml_node(model, path, row_iter): + (name, hours, minutes, seconds, command, next_timer, auto_start) = \ + model.get(row_iter, + PresetsStore._NAME_COL, + PresetsStore._HOURS_COL, + PresetsStore._MINUTES_COL, + PresetsStore._SECONDS_COL, + PresetsStore._COM_COL, + PresetsStore._NEXT_COL, + PresetsStore._AUTO_START_COL + ) + node = et.SubElement(root, 'preset') + node.set('name', name) + node.set('duration', str(hms_to_seconds(hours, minutes, seconds))) + node.set('command', command or '') + node.set('next_timer', next_timer or '') + node.set('auto_start', serialize_bool(auto_start)) + + model.foreach(add_xml_node) + tree = et.ElementTree(root) + + file_dir = path.dirname(file_path) + if not path.exists(file_dir): + print 'Creating config directory: %s' % file_dir + os.makedirs(file_dir, 0744) + tree.write(file_path) + _save_presets = staticmethod(_save_presets) + diff --git a/timer-applet/src/timerapplet/core/Timer.py b/timer-applet/src/timerapplet/core/Timer.py new file mode 100644 index 00000000..f4ba57a2 --- /dev/null +++ b/timer-applet/src/timerapplet/core/Timer.py @@ -0,0 +1,175 @@ +# Copyright (C) 2008 Jimmy Do +# Copyright (C) 2010 Kenny Meyer +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import datetime +import time +import gobject + +class Timer(gobject.GObject): + (STATE_IDLE, STATE_RUNNING, STATE_PAUSED, STATE_FINISHED) = xrange(4) + + __gsignals__ = {'time-changed': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'state-changed': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())} + + def __init__(self): + gobject.GObject.__init__(self) + self._state = Timer.STATE_IDLE + self._duration_seconds = 0 + self._remaining_seconds = 0 + self._end_time = 0 + self._name = '' + self._command = '' + self._next_timer = '' + self._auto_start = False + + def set_duration(self, seconds): + """Set the duration of the timer in seconds.""" + assert self._state == Timer.STATE_IDLE + self._duration_seconds = seconds + + def get_duration(self): + """Return the duration of the timer in seconds.""" + return self._duration_seconds + + def set_name(self, name): + """Set the name of the timer.""" + assert self._state == Timer.STATE_IDLE + self._name = name + + def get_name(self): + """Return the name of the timer.""" + return self._name + + def set_command(self, command): + """Set the command to run of the timer.""" + assert self._state == Timer.STATE_IDLE + self._command = command + + def get_command(self): + """Return the name of the command of the timer.""" + return self._command + + def set_next_timer(self, timer): + """Set the next timeer of the timer.""" + assert self._state == Timer.STATE_IDLE + self._next_timer = timer + + def get_next_timer(self): + """Get the next timer of the timer.""" + return self._next_timer + + def set_auto_start(self, auto_start): + """Set the auto-start value of the timer.""" + assert self._state == Timer.STATE_IDLE + self._auto_start = auto_start + + def get_auto_start(self): + """Get the auto-start value.""" + return self._auto_start + + def start(self): + """Start or resume the timer. + + This method should only be called when the timer is IDLE or PAUSED. + + """ + assert self._state == Timer.STATE_IDLE or self._state == Timer.STATE_PAUSED + self._timer_transition_to_state(Timer.STATE_RUNNING) + + def stop(self): + """Pause the timer. + + This method should only be called when the timer is RUNNING. + + """ + assert self._state == Timer.STATE_RUNNING + self._timer_transition_to_state(Timer.STATE_PAUSED) + + def reset(self): + """Reset the timer. + + This method should only be called when the timer is not IDLE. + + """ + assert self._state != Timer.STATE_IDLE + self._timer_transition_to_state(Timer.STATE_IDLE) + + def get_state(self): + """Return the current state.""" + return self._state + + def get_remaining_time(self): + """Return the remaining time in seconds.""" + return min(self._duration_seconds, max(0, self._remaining_seconds)) + + def get_end_time(self): + """Return a datetime object representing the end time. + + This method should only be called when the timer is RUNNING or FINISHED. + + """ + assert self._state == Timer.STATE_RUNNING or self._state == Timer.STATE_FINISHED + return datetime.datetime.fromtimestamp(self._end_time) + + def _timer_set_state(self, state): + self._state = state + self.emit('state-changed') + + def _timer_transition_to_state(self, dest_state): + cur_time = int(time.time()) + + if dest_state == Timer.STATE_IDLE: + self._end_time = 0 + self._set_remaining_time(self._duration_seconds) + elif dest_state == Timer.STATE_RUNNING: + assert self._duration_seconds >= 0 + + if self._state == Timer.STATE_IDLE: + self._end_time = cur_time + self._duration_seconds + self._set_remaining_time(self._duration_seconds) + elif self._state == Timer.STATE_PAUSED: + self._end_time = cur_time + self._remaining_seconds + + gobject.timeout_add(500, self._on_timeout) + elif dest_state == Timer.STATE_PAUSED: + self._set_remaining_time(self._end_time - cur_time) + self._end_time = 0 + elif dest_state == Timer.STATE_FINISHED: + pass + else: + assert False + + self._timer_set_state(dest_state) + + def _on_timeout(self): + if self._state != Timer.STATE_RUNNING: + return False # remove timeout source + + new_remaining = self._end_time - int(time.time()) + if self._remaining_seconds != new_remaining: + self._set_remaining_time(new_remaining) + + if self._remaining_seconds < 0: + self._timer_transition_to_state(Timer.STATE_FINISHED) + return False # remove timeout source + return True # keep timeout source + + def _set_remaining_time(self, new_remaining): + self._remaining_seconds = new_remaining + self.emit('time-changed') diff --git a/timer-applet/src/timerapplet/core/__init__.py b/timer-applet/src/timerapplet/core/__init__.py new file mode 100644 index 00000000..861b496d --- /dev/null +++ b/timer-applet/src/timerapplet/core/__init__.py @@ -0,0 +1,19 @@ +# Copyright (C) 2008 Jimmy Do +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +from Timer import Timer +from PresetsStore import PresetsStore +from AppletMateConfWrapper import AppletMateConfWrapper -- cgit v1.2.1