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/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 +++ 14 files changed, 1343 insertions(+) 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 (limited to 'timer-applet/src/timerapplet/ui') diff --git a/timer-applet/src/timerapplet/ui/AddEditPresetDialog.py b/timer-applet/src/timerapplet/ui/AddEditPresetDialog.py new file mode 100644 index 00000000..d6773769 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/AddEditPresetDialog.py @@ -0,0 +1,75 @@ +# 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 gtk +import gtk.glade as glade +from DurationChooser import DurationChooser + +class AddEditPresetDialog(object): + def __init__(self, glade_file_name, title, name_validator_func, + name='', hours=0, minutes=0, seconds=0, command='', + next_timer='', auto_start=False): + self._valid_name_func = name_validator_func + + glade_widgets = glade.XML(glade_file_name, 'add_edit_preset_dialog') + self._dialog = glade_widgets.get_widget('add_edit_preset_dialog') + self._ok_button = glade_widgets.get_widget('ok_button') + self._cancel_button = glade_widgets.get_widget('cancel_button') + self._name_entry = glade_widgets.get_widget('name_entry') + duration_chooser_container = glade_widgets.get_widget('duration_chooser_container') + self._duration_chooser = DurationChooser(gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)) + self._command_entry = glade_widgets.get_widget('command_entry') + self._next_timer_entry = glade_widgets.get_widget('next_timer_entry') + self._auto_start_check = glade_widgets.get_widget('auto_start_check') + + duration_chooser_container.pack_start(self._duration_chooser) + + self._dialog.set_title(title) + self._dialog.set_default_response(gtk.RESPONSE_OK) + self._name_entry.set_text(name) + self._command_entry.set_text(command) + self._duration_chooser.set_duration(hours, minutes, seconds) + self._next_timer_entry.set_text(next_timer) + self._auto_start_check.set_active(auto_start) + + self._name_entry.connect('changed', lambda entry: self._check_for_valid_save_preset_input()) + self._duration_chooser.connect('duration-changed', + lambda chooser: self._check_for_valid_save_preset_input()) + self._duration_chooser.show() + + def _non_zero_duration(self): + (hours, minutes, seconds) = self._duration_chooser.get_duration() + return (hours > 0 or minutes > 0 or seconds > 0) + + def _check_for_valid_save_preset_input(self): + self._ok_button.props.sensitive = (self._non_zero_duration() and + self._valid_name_func(self._name_entry.get_text())) + + ## Callback for saving ## + + def get_preset(self): + self._check_for_valid_save_preset_input() + result = self._dialog.run() + self._dialog.hide() + if result == gtk.RESPONSE_OK: + (hours, minutes, seconds) = self._duration_chooser.get_duration() + cmd = self._command_entry.get_text() + next_timer = self._next_timer_entry.get_text() + auto_start = self._auto_start_check.get_active() + return (self._name_entry.get_text(), hours, minutes, seconds, cmd, + next_timer, auto_start) + else: + return None diff --git a/timer-applet/src/timerapplet/ui/ContinueTimerDialog.py b/timer-applet/src/timerapplet/ui/ContinueTimerDialog.py new file mode 100644 index 00000000..a6b1bfb2 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/ContinueTimerDialog.py @@ -0,0 +1,63 @@ +# 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 + +class ContinueTimerDialog(object): + (STOP_TIMER, KEEP_PAUSED, CONTINUE_TIMER) = xrange(3) + + def __init__(self, glade_file_name, header_text, body_text): + self._dialog = gtk.Dialog(_('Continue Timer'), + None, + gtk.DIALOG_DESTROY_WITH_PARENT, + (_('_Stop Timer'), gtk.RESPONSE_NO, + _('_Keep Paused'), gtk.RESPONSE_CLOSE, + _('_Continue Timer'), gtk.RESPONSE_YES)) + self._dialog.props.border_width = 6 + self._dialog.props.has_separator = False + self._dialog.props.resizable = False + self._dialog.vbox.props.spacing = 12 + self._dialog.set_default_response(gtk.RESPONSE_YES) + + hbox = gtk.HBox(False, 0) + hbox.props.spacing = 12 + hbox.props.border_width = 6 + + image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG) + image.props.yalign = 0.0 + + label = gtk.Label('%s\n\n%s' % (header_text, body_text)) + label.props.use_markup = True + label.props.wrap = True + label.props.yalign = 0.0 + + hbox.pack_start(image, False, False, 0) + hbox.pack_start(label, False, False, 0) + self._dialog.vbox.pack_start(hbox, False, False, 0) + + hbox.show_all() + + def get_response(self): + dialog_result = self._dialog.run() + self._dialog.hide() + if dialog_result == gtk.RESPONSE_YES: + return ContinueTimerDialog.CONTINUE_TIMER + elif dialog_result == gtk.RESPONSE_NO: + return ContinueTimerDialog.STOP_TIMER + else: + return ContinueTimerDialog.KEEP_PAUSED + diff --git a/timer-applet/src/timerapplet/ui/DurationChooser.py b/timer-applet/src/timerapplet/ui/DurationChooser.py new file mode 100644 index 00000000..77d0a7ed --- /dev/null +++ b/timer-applet/src/timerapplet/ui/DurationChooser.py @@ -0,0 +1,166 @@ +# 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 math +import gobject +import gtk + +class DurationChooser(gtk.VBox): + __gsignals__ = {'duration-changed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())} + MAX_HOURS = 48 + MAX_MINUTES = 999 + MAX_SECONDS = 999 + + def __init__(self, size_group): + gtk.VBox.__init__(self, False, 6) + + self._hours_spin = self._add_row(_('_Hours:'), + size_group, + DurationChooser.MAX_HOURS) + self._minutes_spin = self._add_row(_('_Minutes:'), + size_group, + DurationChooser.MAX_MINUTES) + self._seconds_spin = self._add_row(_('_Seconds:'), + size_group, + DurationChooser.MAX_SECONDS) + + def get_duration(self): + """Return numerical representations of the values in the spinbuttons. + + This method does not just call get_value_as_int() on each spinbutton because + get_value_as_int() does not return the most up-to-date value if the user is + in the middle of editing the value in a spinbutton. Instead, it actually + returns the value previously "confirmed" for the spinbutton (for example, + by moving focus to another widget). + + Example: User starts with all spinboxes set to 0. He types 5 in 'Minutes' and + immediately uses the keyboard to activate the 'Save as Preset' button. Because + the user never moved focus out of the spinbutton, get_value_as_int() will report + the previous value of 0, and the saved preset will not have the expected duration + of 5 minutes. + + If a particular spinbutton is empty or contains a non-float value, then this method + will report its value as zero. + + Returns a tuple in this format: (hours, minutes, seconds) + + """ + hours_str = self._hours_spin.props.text + minutes_str = self._minutes_spin.props.text + seconds_str = self._seconds_spin.props.text + hours = 0 + minutes = 0 + seconds = 0 + + try: + hours = float(hours_str) + except TypeError: + pass + except ValueError: + pass + + try: + minutes = float(minutes_str) + except TypeError: + pass + except ValueError: + pass + + try: + seconds = float(seconds_str) + except TypeError: + pass + except ValueError: + pass + + minutes_fraction = minutes - int(minutes) + hours_fraction = hours - int(hours) + + # Distribute overflow from seconds to minutes to hours. + if seconds > 59: + minutes = math.floor(seconds / 60) + seconds = seconds % 60 + if minutes > 59: + hours = math.floor(minutes / 60) + minutes = minutes % 60 + if hours > DurationChooser.MAX_HOURS: + hours = DurationChooser.MAX_HOURS + + # Distribute fractions. + if hours_fraction > 0: + minutes = int(hours_fraction * 60) + if minutes_fraction > 0: + seconds = int(minutes_fraction * 60) + + return (int(hours), int(minutes), int(seconds)) + + def set_duration(self, hours, minutes, seconds): + self._hours_spin.set_value(hours) + self._minutes_spin.set_value(minutes) + self._seconds_spin.set_value(seconds) + + def normalize_fields(self): + (hours, minutes, seconds) = self.get_duration() + self._hours_spin.set_value(hours) + self._minutes_spin.set_value(minutes) + self._seconds_spin.set_value(seconds) + + def clear(self): + self._hours_spin.set_value(0) + self._minutes_spin.set_value(0) + self._seconds_spin.set_value(0) + + def focus_hours(self): + self._hours_spin.grab_focus() + + def _add_row(self, row_label_str, size_group, max_spin_val): + row_hbox = gtk.HBox(False, 6) + + label = gtk.Label(row_label_str) + label.set_property('xalign', 0.0) + label.set_property('use-underline', True) + size_group.add_widget(label) + + spin_adj = gtk.Adjustment(0, 0, max_spin_val, 1, 1, 0) + spin_button = gtk.SpinButton(spin_adj, 1.0, 0) + spin_button.props.activates_default = True + spin_button.props.numeric = False + spin_button.props.update_policy = gtk.UPDATE_IF_VALID + + label.set_mnemonic_widget(spin_button) + + row_hbox.pack_start(label, False, False, 0) + row_hbox.pack_start(spin_button, True, True, 0) + self.pack_start(row_hbox, False, False, 0) + + spin_button.connect('changed', self._on_spin_button_val_changed) + spin_button.connect('focus-out-event', self._on_spin_button_focus_out) + spin_button.connect('activate', self._on_spin_button_activate) + + label.show() + spin_button.show() + row_hbox.show() + return spin_button + + def _on_spin_button_val_changed(self, spin_button): + self.emit('duration-changed') + + def _on_spin_button_focus_out(self, spin_button, event): + self.normalize_fields() + + def _on_spin_button_activate(self, entry): + self.normalize_fields() diff --git a/timer-applet/src/timerapplet/ui/Makefile.am b/timer-applet/src/timerapplet/ui/Makefile.am new file mode 100644 index 00000000..0021823e --- /dev/null +++ b/timer-applet/src/timerapplet/ui/Makefile.am @@ -0,0 +1,15 @@ +moduledir = $(pythondir)/timerapplet/ui +module_PYTHON = \ + __init__.py \ + AddEditPresetDialog.py \ + ContinueTimerDialog.py \ + DurationChooser.py \ + ManagePresetsDialog.py \ + Notifier.py \ + PieMeter.py \ + PreferencesDialog.py \ + PulseButton.py \ + ScrollableButtonList.py \ + StartTimerDialog.py \ + StartNextTimerDialog.py \ + StatusButton.py diff --git a/timer-applet/src/timerapplet/ui/ManagePresetsDialog.py b/timer-applet/src/timerapplet/ui/ManagePresetsDialog.py new file mode 100644 index 00000000..0ee38625 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/ManagePresetsDialog.py @@ -0,0 +1,86 @@ +# 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 gobject +import gtk +import gtk.glade as glade + +class ManagePresetsDialog(gobject.GObject): + __gsignals__ = {'clicked-add': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'clicked-edit': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), + 'clicked-remove': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))} + + def __init__(self, glade_file_name, presets_store, preset_display_func): + gobject.GObject.__init__(self) + + glade_widgets = glade.XML(glade_file_name, 'manage_presets_dialog') + self._dialog = glade_widgets.get_widget('manage_presets_dialog') + self._presets_view = glade_widgets.get_widget('presets_view') + self._delete_button = glade_widgets.get_widget('delete_button') + self._edit_button = glade_widgets.get_widget('edit_button') + self._add_button = glade_widgets.get_widget('add_button') + + self._presets_view.set_model(presets_store) + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn('Preset', renderer) + + def preset_cell_data_func(col, cell, model, row_iter, user_data=None): + cell.props.text = preset_display_func(row_iter) + + col.set_cell_data_func(renderer, preset_cell_data_func) + self._presets_view.append_column(col) + + self._dialog.connect('response', self._on_dialog_response) + self._dialog.connect('delete-event', self._dialog.hide_on_delete) + self._presets_view.get_selection().connect('changed', lambda selection: self._update_button_states()) + self._delete_button.connect('clicked', self._on_delete_button_clicked) + self._edit_button.connect('clicked', self._on_edit_button_clicked) + self._add_button.connect('clicked', self._on_add_button_clicked) + + self._update_button_states() + self._dialog.set_default_size(300, 220) + + def show(self): + self._dialog.present() + + def _get_selected_path(self): + selection = self._presets_view.get_selection() + (model, selection_iter) = selection.get_selected() + return model.get_path(selection_iter) + + def _update_button_states(self): + selection = self._presets_view.get_selection() + num_selected = selection.count_selected_rows() + self._delete_button.props.sensitive = num_selected >= 1 + self._edit_button.props.sensitive = num_selected == 1 + + def _on_delete_button_clicked(self, button): + row_path = self._get_selected_path() + self.emit('clicked-remove', row_path) + + def _on_edit_button_clicked(self, button): + row_path = self._get_selected_path() + self.emit('clicked-edit', row_path) + + def _on_add_button_clicked(self, button): + self.emit('clicked-add') + + def _on_dialog_response(self, dialog, response_id): + dialog.hide() + diff --git a/timer-applet/src/timerapplet/ui/Notifier.py b/timer-applet/src/timerapplet/ui/Notifier.py new file mode 100644 index 00000000..92e814b7 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/Notifier.py @@ -0,0 +1,73 @@ +# 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 gobject +import pynotify + +class Notifier(object): + _NOTIFICATION_REDISPLAY_INTERVAL_SECONDS = 60 + + def __init__(self, app_name, icon, attach): + self._icon = icon + self._attach = attach + self._notify = None + self._handler_id = None + self._timeout_id = None + + if not pynotify.is_initted(): + pynotify.init(app_name) + + def begin(self, summary, body, get_reminder_message_func): + # NOTE: This callback wrapper is to workaround an API-breaking change present in + # the version of libmatenotify used by Fedora 10. The API break adds an additional + # 'reason' parameter to the callback signature. This was fixed before + # the latest official release of libmatenotify, but it looks like Fedora 10 + # is using an unofficial libmatenotify build that still contains the API-breaking change. + def closed_callback_wrapper(notification, reason_UNUSED=None): + self._on_notification_closed(notification, get_reminder_message_func) + + self.end() + self._notify = pynotify.Notification(summary, body, self._icon) + self._handler_id = self._notify.connect('closed', closed_callback_wrapper) + self._notify.show() + + def end(self): + if self._notify is not None: + if self._timeout_id is not None: + gobject.source_remove(self._timeout_id) + self._timeout_id = None + self._notify.disconnect(self._handler_id) + self._handler_id = None + try: + self._notify.close() + except gobject.GError: + # Throws a GError exception if the notification bubble has already been closed. + # Ignore the exception. + pass + self._notify = None + + def _on_notification_closed(self, notification, get_reminder_message_func): + self._timeout_id = gobject.timeout_add(Notifier._NOTIFICATION_REDISPLAY_INTERVAL_SECONDS * 1000, + self._on_notification_redisplay_timeout, + get_reminder_message_func) + + def _on_notification_redisplay_timeout(self, get_reminder_message_func): + message = get_reminder_message_func() + self._notify.props.body = message + self._notify.show() + + self._timeout_id = None + return False diff --git a/timer-applet/src/timerapplet/ui/PieMeter.py b/timer-applet/src/timerapplet/ui/PieMeter.py new file mode 100644 index 00000000..f97d02d7 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/PieMeter.py @@ -0,0 +1,77 @@ +# 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 math +import gobject +import gtk + +class PieMeter(gtk.Image): + _DEFAULT_SIZE = 24 + + def __init__(self): + gtk.Image.__init__(self) + self._progress = 0.0 + self._fill_color = (0.0, 1.0, 0.0) + + def set_progress(self, progress): + assert progress >= 0.0 + assert progress <= 1.0 + self._progress = progress + if self.window is not None: + self.window.invalidate_rect(self.allocation, True) + + def set_fill_color(self, red, green, blue): + assert 0.0 <= red <= 1.0 + assert 0.0 <= green <= 1.0 + assert 0.0 <= blue <= 1.0 + self._fill_color = (red, green, blue) + + if self.window is not None: + self.window.invalidate_rect(self.allocation, True) + + def do_size_request(self, requisition): + requisition.width = PieMeter._DEFAULT_SIZE + requisition.height = PieMeter._DEFAULT_SIZE + + def do_expose_event(self, event): + context = event.window.cairo_create() + + rect = self.allocation + x = rect.x + (rect.width / 2) + y = rect.y + (rect.height / 2) + radius = (min(rect.width, rect.height) / 2) + + # Draw background circle + context.arc(x, y, radius, 0, 2 * math.pi) + context.set_source_rgba(0.8, 0.8, 0.8) + context.fill() + + # Draw pie + context.arc(x, y, radius, (-0.5 * math.pi) + self._progress * 2 * math.pi, 1.5 * math.pi) + context.line_to(x, y) + context.close_path() + (red, green, blue) = self._fill_color + context.set_source_rgb(red, green, blue) + context.fill() + + # Draw circle outline + context.arc(x, y, radius, 0, 2 * math.pi) + context.set_source_rgba(1, 1, 1) + context.set_line_width(1.0) + context.stroke() + +gobject.type_register(PieMeter) + diff --git a/timer-applet/src/timerapplet/ui/PreferencesDialog.py b/timer-applet/src/timerapplet/ui/PreferencesDialog.py new file mode 100644 index 00000000..6341fe8d --- /dev/null +++ b/timer-applet/src/timerapplet/ui/PreferencesDialog.py @@ -0,0 +1,192 @@ +# 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 gobject +import gtk +import gtk.glade as glade + +class PreferencesDialog(gobject.GObject): + __gsignals__ = \ + { + 'show-remaining-time-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN,) + ), + 'play-sound-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN,) + ), + 'use-custom-sound-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN,) + ), + 'show-popup-notification-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN,) + ), + 'show-pulsing-icon-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_BOOLEAN,) + ), + 'custom-sound-path-changed': + ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING,) + ) + } + + __gproperties__ = \ + { + 'show-remaining-time': + (bool, + 'Show remaining time', + 'Whether to show remaining time when the timer is running', + False, + gobject.PARAM_WRITABLE + ), + 'play-sound': + (bool, + 'Play notification sound', + 'Whether to play notification sound when the timer is finished', + False, + gobject.PARAM_WRITABLE + ), + 'use-custom-sound': + (bool, + 'Use custom sound', + 'Whether to use a custom notification sound', + False, + gobject.PARAM_WRITABLE + ), + 'show-popup-notification': + (bool, + 'Show Popup notification', + 'Whether to show a popup notifcation when timer finished', + False, + gobject.PARAM_WRITABLE + ), + 'show-pulsing-icon': + (bool, + 'Show pulsing icon', + 'Whether to show pulsing icon when timer finished', + False, + gobject.PARAM_WRITABLE + ), + 'custom-sound-path': + (str, + 'Custom sound path', + 'Path to a custom notification sound', + '', + gobject.PARAM_WRITABLE + ) + } + + def __init__(self, glade_file_name): + gobject.GObject.__init__(self) + glade_widgets = glade.XML(glade_file_name, 'preferences_dialog') + self._preferences_dialog = glade_widgets.get_widget('preferences_dialog') + self._show_time_check = glade_widgets.get_widget('show_time_check') + self._play_sound_check = glade_widgets.get_widget('play_sound_check') + self._use_default_sound_radio = glade_widgets.get_widget('use_default_sound_radio') + self._use_custom_sound_radio = glade_widgets.get_widget('use_custom_sound_radio') + self._sound_chooser_button = glade_widgets.get_widget('sound_chooser_button') + self._play_sound_box = glade_widgets.get_widget('play_sound_box') + #: Popup notification checkbutton + self._popup_notification_check = glade_widgets.get_widget('popup_notification_check') + #: Pulsing icon checkbutton + self._pulsing_icon_check = glade_widgets.get_widget('pulsing_trayicon_check') + + ####################################################################### + # Signals + ####################################################################### + self._show_time_check.connect('toggled', self._on_show_time_check_toggled) + self._play_sound_check.connect('toggled', self._on_play_sound_check_toggled) + self._use_custom_sound_radio.connect('toggled', self._on_use_custom_sound_radio_toggled) + #: Popup notification checkbutton 'toggled' signal + self._popup_notification_check.connect('toggled', self._on_popup_notification_toggled) + #: Pulsing icon checkbutton 'toggled' signal + self._pulsing_icon_check.connect('toggled', self._on_pulsing_icon_toggled) + + self._sound_chooser_button.connect('selection-changed', self._on_sound_chooser_button_selection_changed) + self._preferences_dialog.connect('delete-event', gtk.Widget.hide_on_delete) + self._preferences_dialog.connect('response', lambda dialog, response_id: self._preferences_dialog.hide()) + + def show(self): + self._preferences_dialog.present() + + def _on_show_time_check_toggled(self, widget): + self.emit('show-remaining-time-changed', widget.props.active) + + def _on_play_sound_check_toggled(self, widget): + self.emit('play-sound-changed', widget.props.active) + + def _on_use_custom_sound_radio_toggled(self, widget): + self.emit('use-custom-sound-changed', widget.props.active) + + def _on_popup_notification_toggled(self, widget): + """Emit a signal when `self._popup_notification_check` gets toggled in + the Preferences dialog window.""" + self.emit('show-popup-notification-changed', widget.props.active) + + def _on_pulsing_icon_toggled(self, widget): + """Emit a signal when `self._popup_notification_check` gets toggled in + the Preferences dialog window.""" + self.emit('show-pulsing-icon-changed', widget.props.active) + + def _on_sound_chooser_button_selection_changed(self, chooser_button): + filename = chooser_button.get_filename() + + # Work around an issue where calling set_filename() will cause + # 3 selection-changed signals to be emitted: first two will have a None filename + # and the third will finally have the desired filename. + if filename is not None: + print 'Custom sound changed to path: %s' % filename + self.emit('custom-sound-path-changed', filename) + + def do_set_property(self, pspec, value): + if pspec.name == 'show-remaining-time': + self._show_time_check.props.active = value + elif pspec.name == 'play-sound': + self._play_sound_check.props.active = value + self._play_sound_box.props.sensitive = value + elif pspec.name == 'use-custom-sound': + if value == True: + self._use_custom_sound_radio.props.active = True + self._sound_chooser_button.props.sensitive = True + else: + # Note: Setting _use_custom_sound_radio.props.active to False + # does not automatically set _use_default_sound_radio.props.active to True + self._use_default_sound_radio.props.active = True + self._sound_chooser_button.props.sensitive = False + elif pspec.name == 'show-popup-notification': + self._popup_notification_check.props.active = value + elif pspec.name == 'show-pulsing-icon': + self._pulsing_icon_check.props.active = value + elif pspec.name == 'custom-sound-path': + # Prevent infinite loop of events. + if self._sound_chooser_button.get_filename() != value: + self._sound_chooser_button.set_filename(value) diff --git a/timer-applet/src/timerapplet/ui/PulseButton.py b/timer-applet/src/timerapplet/ui/PulseButton.py new file mode 100644 index 00000000..8a9fe48c --- /dev/null +++ b/timer-applet/src/timerapplet/ui/PulseButton.py @@ -0,0 +1,69 @@ +# 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 math +import time +import gobject +import gtk + +class PulseButton(gtk.Button): + def __init__(self): + super(PulseButton, self).__init__() + + self._anim_period_seconds = 0.7 + self._start_time = 0.0 + self._factor = 0.0 + + def start_pulsing(self): + self._start_time = time.time() + gobject.timeout_add(10, self._on_timeout) + + def stop_pulsing(self): + self._start_time = 0 + + def _on_timeout(self, data=None): + if self._start_time <= 0.0: + return False + if self.window != None: + delta = time.time() - self._start_time + + if delta > self._anim_period_seconds: + delta = self._anim_period_seconds + self._start_time = time.time() + fraction = delta/self._anim_period_seconds + self._factor = math.sin(fraction * math.pi) + + self.window.invalidate_rect(self.allocation, True) + return True + + def do_expose_event(self, event): + gtk.Button.do_expose_event(self, event) + if self._start_time > 0: + context = event.window.cairo_create() + context.rectangle(0, 0, self.allocation.width, self.allocation.height) + + #color = self.style.bg[gtk.STATE_SELECTED] + #color = gtk.gdk.Color(65535, 65535, 65535) + color = gtk.gdk.Color(0, 0, 0) + red = color.red / 65535.0 + green = color.green / 65535.0 + blue = color.blue / 65535.0 + context.set_source_rgba(red, green, blue, self._factor * 0.8) + context.fill() + + return False + +gobject.type_register(PulseButton) diff --git a/timer-applet/src/timerapplet/ui/ScrollableButtonList.py b/timer-applet/src/timerapplet/ui/ScrollableButtonList.py new file mode 100644 index 00000000..2f8bb378 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/ScrollableButtonList.py @@ -0,0 +1,65 @@ +# 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 gtk + +def get_scroll_value_to_reveal_widget(widget_rect, viewport_rect, scroll_val): + if widget_rect.y < scroll_val: + scroll_val = widget_rect.y + elif widget_rect.y + widget_rect.height + 1 > scroll_val + viewport_rect.height: + scroll_val = widget_rect.y + widget_rect.height + 1 - viewport_rect.height + return scroll_val + +class ScrollableButtonList(gtk.ScrolledWindow): + def __init__(self): + gtk.ScrolledWindow.__init__(self) + + self._vadjust = gtk.Adjustment() + self._vbox = gtk.VBox() + self._viewport = gtk.Viewport() + + self.set_vadjustment(self._vadjust) + self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + self._viewport.modify_bg(gtk.STATE_NORMAL, self.style.base[gtk.STATE_NORMAL]) + + self._viewport.add(self._vbox) + self.add(self._viewport) + + self._vbox.show() + self._viewport.show() + + def add_button(self, button): + button.connect('focus-in-event', self._on_focus) + button.connect('clicked', self._on_clicked) + self._vbox.pack_start(button, False, False) + + def get_buttons(self): + return self._vbox.get_children() + + def _on_focus(self, widget, event): + self._scroll_to_widget(widget) + + def _on_clicked(self, widget): + self._scroll_to_widget(widget) + + def _scroll_to_widget(self, widget): + cur_val = self._vadjust.get_value() + new_val = get_scroll_value_to_reveal_widget(widget.allocation, self._viewport.allocation, cur_val) + + if new_val != cur_val: + self._vadjust.set_value(new_val) + self._vadjust.value_changed() + diff --git a/timer-applet/src/timerapplet/ui/StartNextTimerDialog.py b/timer-applet/src/timerapplet/ui/StartNextTimerDialog.py new file mode 100644 index 00000000..ad9aca1b --- /dev/null +++ b/timer-applet/src/timerapplet/ui/StartNextTimerDialog.py @@ -0,0 +1,66 @@ +# -*- mode: python; coding: utf-8; -*- +# 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 + +""" +The Start Next Timer dialog + +When a timer ended and the auto_start option was disabled this dialog shows up. +""" + +from gettext import gettext as _ +import gtk + +class StartNextTimerDialog(object): + def __init__(self, glade_file_name, header_text, body_text): + # TODO: Include next_timer in body_text + self._dialog = gtk.Dialog( + _("Start Next Timer"), + None, + gtk.DIALOG_DESTROY_WITH_PARENT, + (_("_Don't start next timer"), gtk.RESPONSE_CLOSE, + _("_Start next timer"), gtk.RESPONSE_YES)) + self._dialog.props.border_width = 6 + self._dialog.props.has_separator = False + self._dialog.props.resizable = False + self._dialog.vbox.props.spacing = 12 + self._dialog.set_default_response(gtk.RESPONSE_YES) + + hbox = gtk.HBox(False, 0) + hbox.props.spacing = 12 + hbox.props.border_width = 6 + + image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG) + image.props.yalign = 0.0 + + label = gtk.Label('%s\n\n%s' % (header_text, body_text)) + label.props.use_markup = True + label.props.wrap = True + label.props.yalign = 0.0 + + hbox.pack_start(image, False, False, 0) + hbox.pack_start(label, False, False, 0) + self._dialog.vbox.pack_start(hbox, False, False, 0) + + hbox.show_all() + + def get_response(self): + dialog_result = self._dialog.run() + self._dialog.hide() + if dialog_result == gtk.RESPONSE_YES: + return True + else: + return False + diff --git a/timer-applet/src/timerapplet/ui/StartTimerDialog.py b/timer-applet/src/timerapplet/ui/StartTimerDialog.py new file mode 100644 index 00000000..69728c73 --- /dev/null +++ b/timer-applet/src/timerapplet/ui/StartTimerDialog.py @@ -0,0 +1,267 @@ +# 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 gobject +import gtk +import gtk.glade as glade +import gtk.gdk as gdk +import pango + +from gettext import gettext as _ +from shlex import split as shell_tokenize +from subprocess import check_call, CalledProcessError + +from DurationChooser import DurationChooser +from ScrollableButtonList import ScrollableButtonList + +class StartTimerDialog(gobject.GObject): + __gsignals__ = {'clicked-start': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'clicked-cancel': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'clicked-manage-presets': + (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'clicked-save': + (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_STRING, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_INT, + gobject.TYPE_STRING, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN)), + 'clicked-preset': + (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'double-clicked-preset': + (gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,))} + + def __init__(self, glade_file_name, name_validator_func, presets_store, preset_display_func): + gobject.GObject.__init__(self) + + self._valid_name_func = name_validator_func; + self._presets_store = presets_store + self._preset_display_func = preset_display_func + + self._presets_list = ScrollableButtonList() + labels_size_group = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL) + self._duration_chooser = DurationChooser(labels_size_group) + + glade_widgets = glade.XML(glade_file_name, 'start_timer_dialog') + self._dialog = glade_widgets.get_widget('start_timer_dialog') + self._ok_button = glade_widgets.get_widget('ok_button') + name_label = glade_widgets.get_widget('name_label') + self._name_entry = glade_widgets.get_widget('name_entry') + self._save_button = glade_widgets.get_widget('save_button') + duration_chooser_container = glade_widgets.get_widget('duration_chooser_container') + presets_chooser_container = glade_widgets.get_widget('presets_chooser_container') + self._presets_section = glade_widgets.get_widget('presets_section') + #: The TextEntry control for running a custom command + self._command_entry = glade_widgets.get_widget('command_entry') + #: The "Invalid Command" label + self._invalid_cmd_label = glade_widgets.get_widget('invalid_command_label') + #: The next timer combo box + self._next_timer_combo = glade_widgets.get_widget('next_timer_combo_entry') + self._next_timer_combo.set_model(self._presets_store) + self._next_timer_combo.set_text_column(0) # The column to be shown + #: The auto-start check button. + self._auto_start_check = glade_widgets.get_widget('auto_start_check') + + labels_size_group.add_widget(name_label) + self._dialog.set_default_response(gtk.RESPONSE_OK) + duration_chooser_container.pack_start(self._duration_chooser) + presets_chooser_container.pack_start(self._presets_list) + + self._dialog.connect('response', self._on_dialog_response) + self._dialog.connect('delete-event', self._dialog.hide_on_delete) + self._dialog.add_events(gdk.BUTTON_PRESS_MASK) + self._duration_chooser.connect('duration-changed', self._on_duration_changed) + self._name_entry.connect('changed', self._on_name_entry_changed) + self._save_button.connect('clicked', self._on_save_button_clicked) + # Check that executable is valid while inserting text + self._command_entry.connect('changed', self._check_is_valid_command) + self._next_timer_combo.child.connect("changed", + self._on_next_timer_combo_entry_child_changed) + glade_widgets.get_widget('manage_presets_button').connect('clicked', + self._on_manage_presets_button_clicked) + self._presets_store.connect('row-deleted', + lambda model, row_path: self._update_presets_list()) + self._presets_store.connect('row-changed', + lambda model, row_path, row_iter: self._update_presets_list()) + + self._update_presets_list() + self._duration_chooser.show() + self._presets_list.show() + + def show(self): + if not self._dialog.props.visible: + self._duration_chooser.clear() + self._duration_chooser.focus_hours() + self._name_entry.set_text('') + self._check_for_valid_start_timer_input() + self._check_for_valid_save_preset_input() + self._dialog.present() + + def hide(self): + self._dialog.hide() + + def get_control_data(self): + """Return name and duration in a tuple. + + The returned tuple is in this format: + + (name, hours, minutes, seconds, next_timer, auto_start) + + """ + return (self._name_entry.get_text().strip(),) + \ + self._duration_chooser.get_duration() + \ + (self._command_entry.get_text().strip(), + self._next_timer_combo.child.get_text().strip(), + self._auto_start_check.get_active()) + + def set_name_and_duration(self, name, hours, minutes, seconds, *args): + self._name_entry.set_text(name) + self._command_entry.set_text(args[0]) + self._next_timer_combo.child.set_text(args[1]) + self._auto_start_check.set_active(args[2]) + self._duration_chooser.set_duration(hours, minutes, seconds) + + def _update_presets_list(self): + self._check_for_valid_save_preset_input() + + if len(self._presets_store) == 0: + self._presets_section.hide() + + # Make window shrink + self._dialog.resize(1, 1) + else: + self._presets_section.show() + + for button in self._presets_list.get_buttons(): + button.destroy() + + row_iter = self._presets_store.get_iter_first() + while row_iter is not None: + name = self._preset_display_func(row_iter) + label = gtk.Label(name) + label.set_ellipsize(pango.ELLIPSIZE_END) + button = gtk.Button() + button.set_relief(gtk.RELIEF_NONE) + button.add(label) + self._presets_list.add_button(button) + + button.connect('clicked', + self._on_preset_button_clicked, + self._presets_store.get_path(row_iter)) + button.connect('button_press_event', + self._on_preset_button_double_clicked, + self._presets_store.get_path(row_iter)) + + label.show() + button.show() + + row_iter = self._presets_store.iter_next(row_iter) + + def _check_is_valid_command(self, widget, data=None): + """ + Check that input in the command entry TextBox control is a valid + executable. + """ + try: + data = widget.get_text() + executable = shell_tokenize(data)[0] + # Check if command in path, else raise CalledProcessError + # The idea of using `which` to check if a command is in PATH + # originated from the Python mailing list. + check_call(['which', executable]) + self._invalid_cmd_label.set_label('') + except (ValueError, IndexError, CalledProcessError): + self._invalid_cmd_label.set_label(_("Command not found.")) + if data is '': + self._invalid_cmd_label.set_label('') + + def _non_zero_duration(self): + (hours, minutes, seconds) = self._duration_chooser.get_duration() + return (hours > 0 or minutes > 0 or seconds > 0) + + def _check_for_valid_save_preset_input(self): + self._save_button.props.sensitive = (self._non_zero_duration() and + self._valid_name_func(self._name_entry.get_text())) + # TODO: Add validator for next_timer_combo + + def _check_for_valid_start_timer_input(self): + self._ok_button.props.sensitive = self._non_zero_duration() + + def _on_preset_button_clicked(self, button, row_path): + self.emit('clicked-preset', row_path) + + def _on_preset_button_double_clicked(self, button, event, row_path): + """Emit the `double-clicked-preset' signal.""" + # Check that the double-click event shot off on the preset button + if event.type == gdk._2BUTTON_PRESS: + self.emit('double-clicked-preset', row_path) + + def _on_manage_presets_button_clicked(self, button): + self.emit('clicked-manage-presets') + + def _on_duration_changed(self, data=None): + self._check_for_valid_start_timer_input() + self._check_for_valid_save_preset_input() + + def _on_name_entry_changed(self, entry): + self._check_for_valid_save_preset_input() + + def _on_dialog_response(self, dialog, response_id): + if response_id == gtk.RESPONSE_OK: + self._duration_chooser.normalize_fields() + self.emit('clicked-start') + elif response_id == gtk.RESPONSE_CANCEL: + self.emit('clicked-cancel') + self._dialog.hide() + + def _on_save_button_clicked(self, button): + self._duration_chooser.normalize_fields() + (hours, minutes, seconds) = self._duration_chooser.get_duration() + name = self._name_entry.get_text() + command = self._command_entry.get_text() + next_timer = self._next_timer_combo.child.get_text() + auto_start = self._auto_start_check.get_active() + self.emit('clicked-save', name, hours, minutes, seconds, command, + next_timer, auto_start) + + def _on_next_timer_combo_entry_child_changed(self, widget, data=None): + """Validate selection of the Next Timer ComboBoxEntry.""" + modelfilter = self._presets_store.filter_new() + # Loop through all rows in ListStore + # TODO: Using a generator may be more memory efficient in this case. + for row in modelfilter: + # Check that name of preset is the exact match of the the text in + # the ComboBoxEntry + if widget.get_text() == row[0]: + # Yes, it matches! Make the auto-start checkbox sensitive + # (activate it). + self._auto_start_check.set_sensitive(True) + break + else: + # If value of ComboBoxEntry is None then de-activate the + # auto-start checkbox. + self._auto_start_check.set_sensitive(False) + diff --git a/timer-applet/src/timerapplet/ui/StatusButton.py b/timer-applet/src/timerapplet/ui/StatusButton.py new file mode 100644 index 00000000..cde076fe --- /dev/null +++ b/timer-applet/src/timerapplet/ui/StatusButton.py @@ -0,0 +1,101 @@ +# 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 gtk +from PulseButton import PulseButton +from PieMeter import PieMeter + +class StatusButton(PulseButton): + def __init__(self): + PulseButton.__init__(self) + + self._tooltip = gtk.Tooltip() + + self._icon_widget = gtk.Image() + self._pie_meter = PieMeter() + self._label_widget = gtk.Label() + self._visual_box = gtk.HBox() + + self._visual_box.pack_start(self._icon_widget) + self._visual_box.pack_start(self._pie_meter) + + self._layout_box = None + self._use_vertical = None + self.set_use_vertical_layout(False) + + # _pie_meter will default to visible while + # _icon_widget will default to hidden. + self._pie_meter.show() + self._visual_box.show() + self._label_widget.show() + + def set_tooltip(self, tip_text): + self._tooltip.set_text(tip_text) + + def set_label(self, text): + self._label_widget.set_text(text) + + def set_icon(self, image_path): + self._icon_widget.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file_at_size(image_path, -1, 20)) + #elf._icon_widget.set_from_file(image_path) + + def set_use_icon(self, use_icon): + if use_icon: + self._pie_meter.hide() + self._icon_widget.show() + else: + self._pie_meter.show() + self._icon_widget.hide() + + def set_sensitized(self, sensitized): + self._label_widget.props.sensitive = sensitized + + def set_show_remaining_time(self, show_remaining_time): + if show_remaining_time: + self._label_widget.show() + else: + self._label_widget.hide() + + def set_progress(self, progress): + self._pie_meter.set_progress(progress) + + def set_use_vertical_layout(self, use_vertical): + if self._use_vertical == use_vertical: + return + + self._use_vertical = use_vertical + if self._layout_box is not None: + self._layout_box.remove(self._visual_box) + self._layout_box.remove(self._label_widget) + self.remove(self._layout_box) + self._layout_box.destroy() + self._layout_box = None + + new_layout_box = None + if self._use_vertical: + new_layout_box = gtk.VBox(False, 2) + else: + new_layout_box = gtk.HBox(False, 2) + + new_layout_box.pack_start(self._visual_box, True, True, 0) + new_layout_box.pack_start(self._label_widget, False, False, 0) + + self._layout_box = new_layout_box + self.add(self._layout_box) + self._layout_box.show() + + def set_pie_fill_color(self, red, green, blue): + self._pie_meter.set_fill_color(red, green, blue) diff --git a/timer-applet/src/timerapplet/ui/__init__.py b/timer-applet/src/timerapplet/ui/__init__.py new file mode 100644 index 00000000..2b0b05ba --- /dev/null +++ b/timer-applet/src/timerapplet/ui/__init__.py @@ -0,0 +1,28 @@ +# 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 AddEditPresetDialog import AddEditPresetDialog +from ContinueTimerDialog import ContinueTimerDialog +from DurationChooser import DurationChooser +from ManagePresetsDialog import ManagePresetsDialog +from Notifier import Notifier +from PieMeter import PieMeter +from PreferencesDialog import PreferencesDialog +from PulseButton import PulseButton +from ScrollableButtonList import ScrollableButtonList +from StartTimerDialog import StartTimerDialog +from StartNextTimerDialog import StartNextTimerDialog +from StatusButton import StatusButton -- cgit v1.2.1