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 --- timer-applet/src/timerapplet/Makefile.am | 27 + timer-applet/src/timerapplet/__init__.py | 1 + timer-applet/src/timerapplet/config.py | 39 ++ .../timerapplet/controllers/GlobalController.py | 91 +++ .../src/timerapplet/controllers/Makefile.am | 8 + .../src/timerapplet/controllers/TimerApplet.py | 635 +++++++++++++++++++++ .../timerapplet/controllers/TimerManagerService.py | 42 ++ .../src/timerapplet/controllers/TimerService.py | 49 ++ .../src/timerapplet/controllers/__init__.py | 20 + .../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 + timer-applet/src/timerapplet/defs.py.in | 7 + timer-applet/src/timerapplet/logger.py | 23 + .../src/timerapplet/ui/AddEditPresetDialog.py | 75 +++ .../src/timerapplet/ui/ContinueTimerDialog.py | 63 ++ timer-applet/src/timerapplet/ui/DurationChooser.py | 166 ++++++ timer-applet/src/timerapplet/ui/Makefile.am | 15 + .../src/timerapplet/ui/ManagePresetsDialog.py | 86 +++ timer-applet/src/timerapplet/ui/Notifier.py | 73 +++ timer-applet/src/timerapplet/ui/PieMeter.py | 77 +++ .../src/timerapplet/ui/PreferencesDialog.py | 192 +++++++ timer-applet/src/timerapplet/ui/PulseButton.py | 69 +++ .../src/timerapplet/ui/ScrollableButtonList.py | 65 +++ .../src/timerapplet/ui/StartNextTimerDialog.py | 66 +++ .../src/timerapplet/ui/StartTimerDialog.py | 267 +++++++++ timer-applet/src/timerapplet/ui/StatusButton.py | 101 ++++ timer-applet/src/timerapplet/ui/__init__.py | 28 + timer-applet/src/timerapplet/utils.py | 79 +++ 31 files changed, 2813 insertions(+) create mode 100644 timer-applet/src/timerapplet/Makefile.am create mode 100644 timer-applet/src/timerapplet/__init__.py create mode 100644 timer-applet/src/timerapplet/config.py create mode 100644 timer-applet/src/timerapplet/controllers/GlobalController.py create mode 100644 timer-applet/src/timerapplet/controllers/Makefile.am create mode 100644 timer-applet/src/timerapplet/controllers/TimerApplet.py create mode 100644 timer-applet/src/timerapplet/controllers/TimerManagerService.py create mode 100644 timer-applet/src/timerapplet/controllers/TimerService.py create mode 100644 timer-applet/src/timerapplet/controllers/__init__.py 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 create mode 100644 timer-applet/src/timerapplet/defs.py.in create mode 100644 timer-applet/src/timerapplet/logger.py create mode 100644 timer-applet/src/timerapplet/ui/AddEditPresetDialog.py create mode 100644 timer-applet/src/timerapplet/ui/ContinueTimerDialog.py create mode 100644 timer-applet/src/timerapplet/ui/DurationChooser.py create mode 100644 timer-applet/src/timerapplet/ui/Makefile.am create mode 100644 timer-applet/src/timerapplet/ui/ManagePresetsDialog.py create mode 100644 timer-applet/src/timerapplet/ui/Notifier.py create mode 100644 timer-applet/src/timerapplet/ui/PieMeter.py create mode 100644 timer-applet/src/timerapplet/ui/PreferencesDialog.py create mode 100644 timer-applet/src/timerapplet/ui/PulseButton.py create mode 100644 timer-applet/src/timerapplet/ui/ScrollableButtonList.py create mode 100644 timer-applet/src/timerapplet/ui/StartNextTimerDialog.py create mode 100644 timer-applet/src/timerapplet/ui/StartTimerDialog.py create mode 100644 timer-applet/src/timerapplet/ui/StatusButton.py create mode 100644 timer-applet/src/timerapplet/ui/__init__.py create mode 100644 timer-applet/src/timerapplet/utils.py (limited to 'timer-applet/src/timerapplet') diff --git a/timer-applet/src/timerapplet/Makefile.am b/timer-applet/src/timerapplet/Makefile.am new file mode 100644 index 00000000..08f474c0 --- /dev/null +++ b/timer-applet/src/timerapplet/Makefile.am @@ -0,0 +1,27 @@ +SUBDIRS = controllers core ui + +defs.py: defs.py.in Makefile + sed -e "s|\@PACKAGE\@|$(PACKAGE)|" \ + -e "s|\@VERSION\@|$(VERSION)|" \ + -e "s|\@RESOURCESDIR\@|$(pkgdatadir)|" \ + -e "s|\@IMAGESDIR\@|$(datadir)/pixmaps|" \ + -e "s|\@LOCALEDIR\@|$(localedir)|" \ + -e "s|\@GETTEXT_PACKAGE\@|$(GETTEXT_PACKAGE)|" $< > $@ + +EXTRA_DIST = \ + defs.py.in + +CLEANFILES = \ + defs.py + +# Need this so that defs.py is actually created after cleaning. +BUILT_SOURCES = defs.py + +moduledir = $(pythondir)/timerapplet +module_PYTHON = \ + __init__.py \ + config.py \ + defs.py \ + utils.py \ + logger.py + diff --git a/timer-applet/src/timerapplet/__init__.py b/timer-applet/src/timerapplet/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/timer-applet/src/timerapplet/__init__.py @@ -0,0 +1 @@ + diff --git a/timer-applet/src/timerapplet/config.py b/timer-applet/src/timerapplet/config.py new file mode 100644 index 00000000..ca8a4f9d --- /dev/null +++ b/timer-applet/src/timerapplet/config.py @@ -0,0 +1,39 @@ +# 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 + +import os.path as path + +try: + from defs import * +except ImportError: + PACKAGE = 'timer-applet' + VERSION = '0' + GETTEXT_PACKAGE = '' + LOCALE_DIR = '' + RESOURCES_DIR = path.join(path.dirname(__file__), '../../data') + IMAGES_DIR = path.join(path.dirname(__file__), '../../images') + +print 'Using these definitions:' +print 'GETTEXT_PACKAGE: %s' % GETTEXT_PACKAGE +print 'LOCALE_DIR: %s' % LOCALE_DIR +print 'RESOURCES_DIR: %s' % RESOURCES_DIR +print 'IMAGES_DIR: %s' % IMAGES_DIR + +GLADE_PATH = path.join(RESOURCES_DIR, 'timer-applet.glade') +POPUP_MENU_FILE_PATH = path.join(RESOURCES_DIR, 'TimerApplet.xml') +ICON_PATH = path.join(IMAGES_DIR, 'timer-applet.png') +PRESETS_PATH = path.expanduser('~/.config/mate/timer-applet/presets.xml') +DEFAULT_SOUND_PATH = '/usr/share/sounds/gtk-events/clicked.wav' diff --git a/timer-applet/src/timerapplet/controllers/GlobalController.py b/timer-applet/src/timerapplet/controllers/GlobalController.py new file mode 100644 index 00000000..860573a6 --- /dev/null +++ b/timer-applet/src/timerapplet/controllers/GlobalController.py @@ -0,0 +1,91 @@ +# 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 gettext import gettext as _ +import gtk +from timerapplet import core +from timerapplet import ui +from timerapplet import utils +from timerapplet import config + +class GlobalController(object): + def __init__(self): + self._presets_store = core.PresetsStore(config.PRESETS_PATH) + self._manage_presets_dialog = ui.ManagePresetsDialog(config.GLADE_PATH, + self._presets_store.get_model(), + lambda row_iter: utils.get_preset_display_text(self._presets_store, + row_iter)) + + self._manage_presets_dialog.connect('clicked-add', self._on_mgr_clicked_add) + self._manage_presets_dialog.connect('clicked-edit', self._on_mgr_clicked_edit) + self._manage_presets_dialog.connect('clicked-remove', self._on_mgr_clicked_remove) + + gtk.window_set_default_icon_from_file(config.ICON_PATH) + + def get_presets_store(self): + return self._presets_store + + def get_manage_presets_dialog(self): + return self._manage_presets_dialog + + def _on_mgr_clicked_add(self, sender, data=None): + add_dialog = ui.AddEditPresetDialog( + config.GLADE_PATH, + _('Add Preset'), + lambda name: utils.is_valid_preset_name(name, self._presets_store)) + + result = add_dialog.get_preset() + if result is not None: + (name, hours, minutes, seconds, command, next_timer, auto_start) = result + self._presets_store.add_preset(name, hours, minutes, seconds, + command, next_timer, auto_start) + + def _on_mgr_clicked_edit(self, sender, row_path, data=None): + row_iter = self._presets_store.get_model().get_iter(row_path) + (name, hours, minutes, seconds, command, next_timer, auto_start) = \ + self._presets_store.get_preset(row_iter) + + edit_dialog = ui.AddEditPresetDialog(config.GLADE_PATH, + _('Edit Preset'), + lambda name: utils.is_valid_preset_name(name, + self._presets_store, + (name,)), + name, + hours, + minutes, + seconds, + command, + next_timer, + auto_start + ) + + result = edit_dialog.get_preset() + if result is not None: + (name, hours, minutes, seconds, command, next_timer, auto_start) = result + self._presets_store.modify_preset(row_iter, name, hours, minutes, + seconds, command, next_timer, + auto_start) + + def _on_mgr_clicked_remove(self, sender, row_path, data=None): + row_iter = self._presets_store.get_model().get_iter(row_path) + self._presets_store.remove_preset(row_iter) + + # TODO + def _on_mgr_next_timer_is_being_edited(self, sender, row_path, data=None): + """Show a dropdown widget to help completing the next timer.""" + raise NotImplementedError("Not implemented, yet") + + diff --git a/timer-applet/src/timerapplet/controllers/Makefile.am b/timer-applet/src/timerapplet/controllers/Makefile.am new file mode 100644 index 00000000..bb5018d6 --- /dev/null +++ b/timer-applet/src/timerapplet/controllers/Makefile.am @@ -0,0 +1,8 @@ +moduledir = $(pythondir)/timerapplet/controllers +module_PYTHON = \ + __init__.py \ + GlobalController.py \ + TimerApplet.py \ + TimerManagerService.py \ + TimerService.py + diff --git a/timer-applet/src/timerapplet/controllers/TimerApplet.py b/timer-applet/src/timerapplet/controllers/TimerApplet.py new file mode 100644 index 00000000..3c53754a --- /dev/null +++ b/timer-applet/src/timerapplet/controllers/TimerApplet.py @@ -0,0 +1,635 @@ +# 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 + +from gettext import gettext as _ +from gettext import ngettext +from datetime import datetime, timedelta +import mateapplet +import gst +import gtk +import gtk.glade as glade +import gtk.gdk as gdk +import subprocess +import shlex +import threading +from timerapplet import config +from timerapplet import core +from timerapplet import ui +from timerapplet import utils + +def on_widget_button_press_event(sender, event, data=None): + if event.button != 1: + sender.emit_stop_by_name('button-press-event') + return False + +def force_no_focus_padding(widget): + gtk.rc_parse_string('\n' + ' style "timer-applet-button-style"\n' + ' {\n' + ' GtkWidget::focus-line-width=0\n' + ' GtkWidget::focus-padding=0\n' + ' }\n' + '\n' + ' widget "*.timer-applet-button" style "timer-applet-button-style"\n' + '\n') + widget.set_name('timer-applet-button') + +class TimerApplet(object): + # MateConf key identifiers + ## You can find Timer Applet's schemas file in data/timer-applet.schemas.in + _SHOW_REMAINING_TIME_KEY = 'show_remaining_time' + _PLAY_SOUND_KEY = 'play_notification_sound' + _USE_CUSTOM_SOUND_KEY = 'use_custom_notification_sound' + _SHOW_POPUP_NOTIFICATION_KEY = 'show_popup_notification' + _SHOW_PULSING_ICON_KEY = 'show_pulsing_icon' + _CUSTOM_SOUND_PATH_KEY = 'custom_notification_sound_path' + + _PRESETS_PLACEHOLDER_NAME = 'Placeholder' + _PRESETS_PLACEHOLDER_PATH = '/popups/popup/Presets/' + _PRESETS_PLACEHOLDER_NAME + _PRESETS_PATH = '/popups/popup/Presets' + + def __init__(self, presets_store, manage_presets_dialog, applet, timer, mateconf_wrapper): + self._presets_store = presets_store + self._manage_presets_dialog = manage_presets_dialog + self._applet = applet + self._timer = timer + + self._gst_playbin = gst.element_factory_make('playbin', 'player') + def bus_event(bus, message): + t = message.type + if t == gst.MESSAGE_EOS: + self._gst_playbin.set_state(gst.STATE_NULL) + elif t == gst.MESSAGE_ERROR: + self._gst_playbin.set_state(gst.STATE_NULL) + err, debug = message.parse_error() + print 'Error playing sound: %s' % err, debug + return True + self._gst_playbin.get_bus().add_watch(bus_event) + + self._status_button = ui.StatusButton() + self._notifier = ui.Notifier('TimerApplet', gtk.STOCK_DIALOG_INFO, self._status_button) + self._start_next_timer_dialog = ui.StartNextTimerDialog( + config.GLADE_PATH, + "Start next timer", + "Would you like to start the next timer?") + self._start_timer_dialog = ui.StartTimerDialog(config.GLADE_PATH, + lambda name: utils.is_valid_preset_name(name, + self._presets_store), + self._presets_store.get_model(), + lambda row_iter: utils.get_preset_display_text(self._presets_store, + row_iter)) + self._continue_dialog = ui.ContinueTimerDialog(config.GLADE_PATH, + _('Continue timer countdown?'), + _('The timer is currently paused. Would you like to continue countdown?')) + self._preferences_dialog = ui.PreferencesDialog(config.GLADE_PATH) + self._mateconf = mateconf_wrapper + + self._about_dialog = glade.XML(config.GLADE_PATH, 'about_dialog').get_widget('about_dialog') + self._about_dialog.set_version(config.VERSION) + + self._applet.set_applet_flags(mateapplet.EXPAND_MINOR) + self._applet.setup_menu_from_file( + None, + config.POPUP_MENU_FILE_PATH, + None, + [('PauseTimer', lambda component, verb: self._timer.stop()), + ('ContinueTimer', lambda component, verb: self._timer.start()), + ('StopTimer', lambda component, verb: self._timer.reset()), + ('RestartTimer', lambda component, verb: self._restart_timer()), + ('StartNextTimer', lambda component, verb: self._start_next_timer()), + ('ManagePresets', lambda component, verb: self._manage_presets_dialog.show()), + ('Preferences', lambda component, + verb: self._preferences_dialog.show()), + ('About', lambda component, verb: self._about_dialog.show())] + ) + self._applet.add(self._status_button) + + # Remove padding around button contents. + force_no_focus_padding(self._status_button) + + # TODO: + # Fix bug in which button would not propogate middle-clicks + # and right-clicks to the applet. + self._status_button.connect('button-press-event', on_widget_button_press_event) + + self._status_button.set_relief(gtk.RELIEF_NONE) + self._status_button.set_icon(config.ICON_PATH); + + self._connect_signals() + self._update_status_button() + self._update_popup_menu() + self._update_preferences_dialog() + self._status_button.show() + self._applet.show() + + def _connect_signals(self): + self._applet.connect('change-orient', lambda applet, orientation: self._update_status_button()) + self._applet.connect('change-size', lambda applet, size: self._update_status_button()) + self._applet.connect('change-background', self._on_applet_change_background) + self._applet.connect('destroy', self._on_applet_destroy) + + self._presets_store.get_model().connect('row-deleted', + lambda model, + row_path: self._update_popup_menu()) + self._presets_store.get_model().connect('row-changed', + lambda model, + row_path, + row_iter: self._update_popup_menu()) + + self._timer.connect('time-changed', self._on_timer_time_changed) + self._timer.connect('state-changed', self._on_timer_state_changed) + self._status_button.connect('clicked', self._on_status_button_clicked) + self._start_timer_dialog.connect('clicked-start', + self._on_start_dialog_clicked_start) + self._start_timer_dialog.connect('clicked-manage-presets', + self._on_start_dialog_clicked_manage_presets) + self._start_timer_dialog.connect('clicked-save', + self._on_start_dialog_clicked_save) + self._start_timer_dialog.connect('clicked-preset', + self._on_start_dialog_clicked_preset) + self._start_timer_dialog.connect('double-clicked-preset', + self._on_start_dialog_double_clicked_preset) + + self._preferences_dialog.connect('show-remaining-time-changed', self._on_prefs_show_time_changed) + self._preferences_dialog.connect('play-sound-changed', self._on_prefs_play_sound_changed) + self._preferences_dialog.connect('use-custom-sound-changed', self._on_prefs_use_custom_sound_changed) + self._preferences_dialog.connect('show-popup-notification-changed', self._on_prefs_show_popup_notification_changed) + self._preferences_dialog.connect('show-pulsing-icon-changed', self._on_prefs_show_pulsing_icon_changed) + self._preferences_dialog.connect('custom-sound-path-changed', self._on_prefs_custom_sound_path_changed) + + self._about_dialog.connect('delete-event', gtk.Widget.hide_on_delete) + self._about_dialog.connect('response', lambda dialog, response_id: self._about_dialog.hide()) + + self._mateconf.add_notification(TimerApplet._SHOW_REMAINING_TIME_KEY, self._on_mateconf_changed) + self._mateconf.add_notification(TimerApplet._PLAY_SOUND_KEY, self._on_mateconf_changed) + self._mateconf.add_notification(TimerApplet._USE_CUSTOM_SOUND_KEY, self._on_mateconf_changed) + self._mateconf.add_notification(TimerApplet._SHOW_PULSING_ICON_KEY, self._on_mateconf_changed) + self._mateconf.add_notification(TimerApplet._SHOW_POPUP_NOTIFICATION_KEY, self._on_mateconf_changed) + self._mateconf.add_notification(TimerApplet._CUSTOM_SOUND_PATH_KEY, self._on_mateconf_changed) + + ## Private methods for updating UI ## + + def _update_status_button(self): + current_state = self._timer.get_state() + if current_state == core.Timer.STATE_IDLE: + print 'Idle' + # This label text should not be visible because the label + # is hidden when the timer is idle. + self._status_button.set_label('--:--:--') + self._status_button.set_tooltip(_('Click to start a new timer countdown.')) + elif current_state == core.Timer.STATE_RUNNING: + print 'Running' + elif current_state == core.Timer.STATE_PAUSED: + print 'Paused' + self._status_button.set_tooltip(_('Paused. Click to continue timer countdown.')) + elif current_state == core.Timer.STATE_FINISHED: + print 'Finished' + self._status_button.set_label(_('Finished')) + name_str = self._timer.get_name() + time_str = utils.get_display_text_from_datetime(self._timer.get_end_time()) + if len(name_str) > 0: + # "" finished at