# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: execrable@gmail.com-20100915233003-y2pvtdvr0jebjl1x # target_branch: bzr+ssh://bazaar.launchpad.net/~exaile-\ # devel/exaile/exaile-0.3.x/ # testament_sha1: af8a2d2a064847dd1cda079390bc2dcaf1b2c073 # timestamp: 2010-09-15 16:32:13 -0700 # base_revision_id: reacocard@gmail.com-20100906220241-\ # 73t2ky14rm9fzefc # # Begin patch === modified file 'plugins/multialarmclock/PLUGININFO' --- plugins/multialarmclock/PLUGININFO 2010-06-15 17:15:07 +0000 +++ plugins/multialarmclock/PLUGININFO 2010-09-15 23:30:03 +0000 @@ -1,4 +1,4 @@ -Version='0.1.2' +Version='0.2' Authors=['Brian Parma '] Name=_('Multi-Alarm Clock') Description=_('Plays music at specific times and days.\n\nNote that when the specified time arrives, Exaile will just act like you pressed the play button, so be sure you have the music you want to hear in your playlist') === modified file 'plugins/multialarmclock/__init__.py' --- plugins/multialarmclock/__init__.py 2010-07-11 13:21:09 +0000 +++ plugins/multialarmclock/__init__.py 2010-09-15 23:30:03 +0000 @@ -1,6 +1,6 @@ #!/usr/bin/python -# Modified 2009 by Brian Parma +# Copyright (C) 2010 by Brian Parma # Copyright (C) 2006 Adam Olsen # # This program is free software; you can redistribute it and/or modify @@ -20,49 +20,73 @@ from __future__ import with_statement import gtk, time, glib, thread, os from xl.nls import gettext as _ -from xl import event, player, settings, xdg -#import xl.plugins as plugins -#import xl.path as xlpath -#import cPickle as pickle - -PATH = os.path.dirname(os.path.realpath(__file__)) -UI = os.path.join(PATH, 'alarmclk.ui') - -pb = gtk.gdk.pixbuf_new_from_file(os.path.join(PATH,'clock32.png')) - -#PLUGIN_NAME = _("Multi-Alarm Clock") -#PLUGIN_AUTHORS = ['Brian Parma '] -#PLUGIN_VERSION = "0.1" -#PLUGIN_ICON = pb -#PLUGIN_DESCRIPTION = _(r"""Plays music at a specific times and days.\n\nNote that when the - #specified time arrives, Exaile will just act like you pressed the play button, - #so be sure you have the music you want to hear in your playlist""") +from xlgui import guiutil +from xl import event, player, settings, xdg, common +from functools import wraps +import logging +import macprefs +from cellrenderers import CellRendererDays + +# We want to use json to write alarms to files, cuz it's prettier +# if we're on python 2.5 it's not available... +try: + import json + _write = lambda x: json.dumps(x, indent=2) + _read = json.loads +except ImportError: + _write = str + _read = lambda x: eval(x, {'__builtin__':None}) + +logger = logging.getLogger("multi-alarmclock") PLUGIN_ENABLED = False -#SETTINGS = None TIMER_ID = None -#RANG = dict() MENU_ITEM = None -#exaile = None - - +ALARM_CLOCK_MAIN = None +MY_BUILDER = None + +def _init(prefsdialog, builder): + '''Since I don't know if init() or enable() will be called first, save builder and setup UI if enabled() was already called.''' + logger.debug('_init() called') + global MY_BUILDER, ALARM_CLOCK_MAIN + + # note that we get a new builder everytime the prefs dialog is closed and re-opened + MY_BUILDER = builder + if ALARM_CLOCK_MAIN is not None: + ALARM_CLOCK_MAIN.init_ui(builder) + +def get_preferences_pane(): + '''Return prefs pane for preferences dialog''' + logger.debug('get_preferences_pane() called') + macprefs.init = _init + return macprefs + +def idle_add(f): + '''Decorator that runs a function through glib.idle_add''' + @wraps(f) + def idler(*args, **kwargs): + glib.idle_add(f, *args, **kwargs) + + return idler + ###><><><### Alarm Clock Stuph ###><><><### class Alarm: ''' Class for individual alarms. ''' - def __init__(self, time="09:00", days=None, name="New Alarm", dict={}): - self.active = True + def __init__(self, time="09:00", days=None, name="New Alarm", active=True, dict={}): + self.active = active self.time = time self.name = name if days is None: - self.days = [False,False,False,False,False,False,False] + self.days = [True]*7 else: self.days = days # For setting attributes by dictionary self.__dict__.update(dict) + def on(self): self.active = True @@ -79,273 +103,259 @@ class AlarmClock: ''' - Class that contains all settings info and displays the main window. + Class that handles the TreeView interaction and keeps track of alarms. ''' def __init__(self, exaile): - self.RANG = {} - self.alarm_list = [] - self.fading = True - self.restart = True - self.min_volume = 0 - self.max_volume = 100 - self.increment = 5 - self.time_per_inc = 1 - self.window = None + self.RANG = {} # Keep track of alarms that have gone off self.exaile = exaile - - self.icon = pb - +# self.view_window = None +# self.view = None + # Create Model - self.model = gtk.ListStore(str,gtk.gdk.Pixbuf,object) - + self.model = gtk.ListStore(bool, str, str, object, str) + # Load any saved alarms self.load_list() - def minvolume_changed(self, widget): - self.min_volume = widget.get_value() - settings.set_option('plugin/multialarmclock/fade_min_volume', - self.min_volume) -# print 'AC: minvol change',self.min_volume,SETTINGS.get_float("alarm_min_volume", plugin=plugins.name(__file__)) - - def maxvolume_changed(self, widget): - self.max_volume = widget.get_value() - settings.set_option('plugin/multialarmclock/fade_max_volume', - self.max_volume) -# print 'AC: maxvol change' - - def increment_changed(self, widget): - self.increment = widget.get_value() - settings.set_option('plugin/multialarmclock/fade_increment', - self.increment) -# print 'AC: inc change' - - def time_changed(self, widget): - self.time_per_inc = widget.get_value() - settings.set_option('plugin/multialarmclock/fade_time_per_inc', - self.time_per_inc) -# print 'AC: time change' - - def selection_change(self, selection): - model, tree_iter = self.selection.get_selected() - if tree_iter is not None: - alarm = model.get_value(tree_iter, 2) - self.EnabledCB.set_active(alarm.active) - days = ['Su','M','T','W','Th','F','S'] - day_string = ' - ' - for i,day in enumerate(alarm.days): - if day: - day_string += days[i] - self.AlarmLabel.set_text(alarm.name+' - '+alarm.time + day_string) -# print 'AC: selection change' - - def fading_cb(self, widget): - self.fading = widget.get_active() - settings.set_option('plugin/multialarmclock/fading_on', - self.fading) -# print 'AC: fading ',self.fading - - def enable_cb(self, widget): - model, tree_iter = self.selection.get_selected() - if tree_iter is not None: - alarm = model.get_value(tree_iter, 2) - alarm.active = widget.get_active() -# print 'AC: toggle: ', alarm.active - + def _create_view(self): + '''Create treeview to display model''' + # Create View + view = gtk.TreeView() + view.set_model(self.model) + + # setup view + cr = gtk.CellRendererToggle() + cr.connect('toggled', self.enable_cb) + col = gtk.TreeViewColumn('Enabled', cr, active=0) + view.append_column(col) + + cr = gtk.CellRendererText() + cr.connect('edited', self.text_edited, 1) + cr.set_property('editable', True) + col = gtk.TreeViewColumn('Name', cr, text=1) + view.append_column(col) + + cr = gtk.CellRendererText() + cr.connect('edited', self.text_edited, 2) + cr.set_property('editable', True) + col = gtk.TreeViewColumn('Time', cr, text=2) + view.append_column(col) + + # custom CellRenderer for Days Popup + cr = CellRendererDays() + cr.connect('days-changed', self.days_changed) + cr.set_property('editable', True) + col = gtk.TreeViewColumn('Days', cr, days=3, text=4) + view.append_column(col) + + return view + + def enable_cb(self, cell, path): + '''Callback for toggling an alarm on/off''' + active = self.model[path][0] + self.model[path][0] = not active + + logger.debug('Alarm {0} {1}abled.'.format(self.model[path][1], 'dis' if active else 'en')) + + # save change + self.save_list() + + + def init_ui(self, builder): + '''Called by exaile to initialize prefs pane. Set up pefs UI''' + logger.debug('init_ui() called.') + +# if self.view_window is not None: + # already setup +# return + + # grab widgets + view_window = builder.get_object('alarm_scrolledwindow') + add_button = builder.get_object('add_button') + del_button = builder.get_object('remove_button') + + # when a plugin is disabled and re-enabled, the preferences pane is not re-created until the prefs dialog is closed + # so if we recycle our class and create a new one, we have to replace the old TreeView with our new one. + # NOTE: reloading the plugin from prefs page (DEBUG MODE) breaks this anyway + child = view_window.get_child() + view = self._create_view() + if child is not None: + logger.debug('stale treeview found, replacing...') + guiutil.gtk_widget_replace(child, view) else: - widget.set_active(False) -# print 'AC: notoggle' - - def restart_cb(self, widget): - self.restart = widget.get_active() - settings.set_option('plugin/multialarmclock/restart_playlist_on', - self.restart) -# print 'AC: restart: ',self.restart - - def show_ui(self, widget, exaile): - ''' - Display main window, which is not Modal. - ''' - if self.window: - self.window.present() - return - - self.signals = {'on_AddButton_clicked':self.add_button, - 'on_EditButton_clicked':self.edit_button, - 'on_DeleteButton_clicked':self.delete_button, - 'on_EnabledCB_toggled':self.enable_cb, - 'on_RestartCB_toggled':self.restart_cb, - 'on_FadingCB_toggled':self.fading_cb, - 'on_MinVolume_value_changed':self.minvolume_changed, - 'on_MaxVolume_value_changed':self.maxvolume_changed, - 'on_Increment_value_changed':self.increment_changed, - 'on_Time_value_changed':self.time_changed, - 'on_MainWindow_destroy':self.destroy} - - self.ui = gtk.Builder() - self.ui.add_from_file(UI) - self.ui.connect_signals(self.signals) # connect signals to GUI - - self.window = self.ui.get_object('MainWindow') - - # Model & Treeview - model created in init() - self.view = self.ui.get_object('AlarmList') - self.view.set_model(self.model) - self.selection = self.view.get_selection() - - col = gtk.TreeViewColumn('Test') - cel = gtk.CellRendererText() - pcel = gtk.CellRendererPixbuf() - - self.view.append_column(col) - - col.pack_start(pcel,False) - col.pack_end(cel,False) - col.add_attribute(cel, 'text', 0) - col.add_attribute(pcel, 'pixbuf', 1) - - # Set GUI Values - self.load_settings() - self.ui.get_object('FadingCB').set_active(self.fading) - self.ui.get_object('RestartCB').set_active(self.restart) - self.ui.get_object('MinVolume').set_value(self.min_volume) - self.ui.get_object('MaxVolume').set_value(self.max_volume) - self.ui.get_object('Increment').set_value(self.increment) - self.ui.get_object('Time').set_value(self.time_per_inc) - self.EnabledCB = self.ui.get_object('EnabledCB') - self.AlarmLabel = self.ui.get_object('AlarmLabel') - - # Set Signal for Selection Change - self.selection.connect('changed', self.selection_change) - - self.window.show_all() + view_window.add(view) + + # signals + add_button.connect('clicked', self.add_button) + del_button.connect('clicked', self.delete_button, view.get_selection()) + + def days_changed(self, cr, path, days): + '''Callback for change of selected days for selected alarm''' + # update model + self.model[path][3] = days + + + # update display + days_str = ['Su','M','Tu','W','Th','F','Sa'] + self.model[path][4] = ','.join( [ days_str[i] for i in range(0,7) if days[i] ] ) + + # save changes + self.save_list() + + + def text_edited(self, cr, path, new_text, idx): + '''Callback for edit of text columns (name and time)''' + old_text = self.model[path][idx] + if old_text == new_text: + return # No change + + if idx == 1: # Name edit + self.model[path][1] = new_text + + # save change + self.save_list() + + elif idx == 2: # Time edit + # validate + try: + t = time.strptime(new_text, '%H:%M') + new_text = time.strftime('%H:%M', t) # ensure consistent 0-padding + except ValueError: + logger.warning('Invalid time format, use: HH:MM (24-hour)') + return + + # update + self.model[path][2] = new_text + + # save change + self.save_list() + def add_alarm(self, alarm): - self.model.append([alarm.name,self.icon,alarm]) - self.alarm_list.append(alarm) + '''Add an alarm to the model''' + # update display + days_str = ['Su','M','Tu','W','Th','F','Sa'] + day_disp = ','.join( [ days_str[i] for i in range(0,7) if alarm.days[i] ] ) + self.model.append([alarm.active, alarm.name, alarm.time, alarm.days, day_disp]) + + # save list changes - NO, called by loader +# self.save_list() def add_button(self, widget): - alarm = Alarm() - add = AddAlarm() # create new instance each time or use a self.instance? - if add.run(alarm): - self.add_alarm(alarm) - self.save_list() # since exaile doesn't notify on program exit... - - def edit_button(self, widget): - # Get currently selected alarm - model, tree_iter = self.selection.get_selected() - if tree_iter is not None: - alarm = model.get_value(tree_iter, 2) - add = AddAlarm() # create new instance each time or use a self.instance? - if add.run(alarm): - model.set_value(tree_iter, 0, alarm.name) # update display incase of name change - self.selection_change(self.selection) - self.save_list() - - def delete_button(self, widget): - model, tree_iter = self.selection.get_selected() - if tree_iter is not None: - alarm = model.get_value(tree_iter, 2) + '''Callback for clicking add button''' + # generate unique name + names = [ row[1] for row in self.model ] + base = 'Alarm' + name = base + i = 0 + while name in names: + i = i+1 + name = base + ' {0}'.format(i) + + # add the new alarm + alarm = Alarm(name=name) + self.add_alarm(alarm) + + # save list changes + self.save_list() + + def delete_button(self, widget, selection): + '''Callback for clicking the delete button''' + # get selected row + model, tree_iter = selection.get_selected() + if tree_iter is not None: model.remove(tree_iter) - self.alarm_list.remove(alarm) + # save list changes self.save_list() + else: + logger.info('No alarm selected for removal.') - def load_settings(self): - print 'AC: load settings' - self.fading = settings.get_option( - 'plugin/multialarmclock/fading_on', self.fading) - self.min_volume = settings.get_option( - 'plugin/multialarmclock/fade_min_volume', self.min_volume) - self.max_volume = settings.get_option( - 'plugin/multialarmclock/fade_max_volume', self.max_volume) - self.increment = settings.get_option( - 'plugin/multialarmclock/fade_increment', self.increment) - self.time_per_inc = settings.get_option( - 'plugin/multialarmclock/fade_time_per_inc', self.time_per_inc) - self.restart = settings.get_option( - 'plugin/multialarmclock/restart_playlist_on', self.restart) def load_list(self): + '''Load alarms from file''' + logger.debug('load_list() called.') path = os.path.join(xdg.get_data_dirs()[0],'alarmlist.dat') try: # Load Alarm List from file. with open(path,'rb') as f: - for line in f.readlines(): + raw = f.read() + try: + alist = _read(raw) + assert isinstance(alist, list) # should be list of dicts (new format) + + except: try: - al = Alarm(dict=eval(line,{'__builtin__':None})) -# print 'AC: loaded - ',al.__dict__ - self.add_alarm(al) - except: - print 'MultiAlarmClock plugin: bad alarm definition' + # try to import old format + for line in raw.strip().split('\n'): + a = Alarm(dict=eval(line, {'__builtin__':None})) + logger.debug('loaded alarm {0} ({1}) from file.'.format(a.name, a.time)) + self.add_alarm(a) + + # force save in new format + logger.info('Old alarm file format found, converting.') + self.save_list() + + except Exception, (e): + logger.warning('Failed to load alarm data from file: {0}'.format(e)) + + else: + for a in alist: + alarm = Alarm(dict=a) + logger.debug('loaded alarm {0} ({1}) from file.'.format(alarm.name, alarm.time)) + self.add_alarm(alarm) except IOError, (e,s): # File might not exist - print 'MultiAlarmClock plugin: could not open file:', s + logger.warning('Could not open file: {0}'.format( s)) + @idle_add def save_list(self): + '''Save alarms to file''' + logger.debug('save_list() called.') + # Save List path = os.path.join(xdg.get_data_dirs()[0],'alarmlist.dat') - if len(self.alarm_list) > 0: + + if len(self.model) > 0: + alist = [ { + 'active':row[0], + 'name':row[1], + 'time':row[2], + 'days':row[3] + } for row in self.model ] + with open(path,'wb') as f: - f.writelines((str(al.__dict__)+'\n' for al in self.alarm_list)) - - def destroy(self, widget): - self.window = None - -class AddAlarm: - def __init__(self): - pass - - def run(self, alarm): - self.ui = gtk.Builder() - self.ui.add_from_file(UI) - - self.window = self.ui.get_object('AddWindow') - self.alarm_name = self.ui.get_object('AlarmName') - self.alarm_hour = self.ui.get_object('SpinHour') - self.alarm_minute = self.ui.get_object('SpinMinute') - self.alarm_days = [self.ui.get_object('Check0'), - self.ui.get_object('Check1'), - self.ui.get_object('Check2'), - self.ui.get_object('Check3'), - self.ui.get_object('Check4'), - self.ui.get_object('Check5'), - self.ui.get_object('Check6')] - - hour, minute = alarm.time.split(':') - self.alarm_hour.set_value(int(hour)) - self.alarm_minute.set_value(int(minute)) - self.alarm_name.set_text(alarm.name) - for i in range(7): - self.alarm_days[i].set_active(alarm.days[i]) - - result = self.window.run() - - # stuff - if result == 1: # press Ok - hour = self.alarm_hour.get_value() - minute = self.alarm_minute.get_value() - alarm.time = '%02d:%02d' % (hour,minute) - alarm.name = unicode(self.alarm_name.get_text(), 'utf-8') - for i in range(7): - alarm.days[i] = self.alarm_days[i].get_active() - - self.window.destroy() - - return (result == 1) + f.write(_write(alist)) + logger.debug('saving {0} alarms.'.format(len(alist))) + ###><><><### Globals ###><><><### +# This is here because sometimes get_prefs_pane gets called before _enabled +#ALARM_CLOCK_MAIN = AlarmClock(exaile) + +@common.threaded def fade_in(main, exaile): - temp_volume = main.min_volume - while temp_volume <= main.max_volume: - #print "AC: set volume to %s" % str(temp_volume) + '''Fade exaile's volume from min to max''' + logger.debug('fade_in() called.') + + # pull settings + temp_volume = settings.get_option('plugin/multialarmclock/fade_min_volume') + fade_max_volume = settings.get_option('plugin/multialarmclock/fade_max_volume') + fade_inc = settings.get_option('plugin/multialarmclock/fade_increment') + time_per_inc = settings.get_option('plugin/multialarmclock/fade_time') / ((fade_max_volume-temp_volume)/fade_inc) + + while temp_volume < fade_max_volume: + logger.debug('set volume to {0}'.format(temp_volume)) + player.PLAYER.set_volume( ( temp_volume ) ) - temp_volume += main.increment - time.sleep( main.time_per_inc ) + temp_volume += fade_inc + time.sleep( time_per_inc ) if player.PLAYER.is_paused() or not player.PLAYER.is_playing(): return + player.PLAYER.set_volume( ( fade_max_volume ) ) def check_alarms(main, exaile): """ @@ -353,30 +363,50 @@ nothing. If the current time matches the time specified and the current day is selected, it starts playing """ + + logger.debug('check_alarms() called') + if not main: return True # TODO: new way? - current = time.strftime("%H:%M", time.localtime()) currentDay = int(time.strftime("%w", time.localtime())) - - for al in main.alarm_list: + + # generate list of alarms from model + alist = [ Alarm(active=row[0], name=row[1], time=row[2], days=row[3]) for row in main.model ] +# print current , [ a.time for a in alist if a.active ] + for al in alist: if al.active and al.time == current and al.days[currentDay] == True: check = time.strftime("%m %d %Y %H:%M") # clever... - if main.RANG.has_key(check): return True + + if main.RANG.has_key(check): + logger.debug('Alarm {0} in RANG'.format(al.name)) + return True + + logger.info('Alarm {0} hit.'.format(al.name)) # tracks to play? - count = len(player.QUEUE.get_tracks()) + count = len(player.QUEUE) if player.QUEUE.current_playlist: - count += len(player.QUEUE.current_playlist.get_tracks()) + count += len(player.QUEUE.current_playlist) else: - count += len(exaile.gui.main.get_selected_page().playlist.get_tracks()) - print 'count:', count - if count == 0 or player.PLAYER.is_playing(): return True # Check if there are songs in playlist and if it is already playing - if main.fading: - thread.start_new(fade_in, (main, exaile)) - if main.restart: + count += len(exaile.gui.main.get_selected_page().playlist) + + if count == 0: + logger.warning('No tracks queued for alarm to play.') + return True + + if player.PLAYER.is_playing(): # Check if there are songs in playlist and if it is already playing + logger.info('Alarm hit, but already playing') + return True + + if settings.get_option('plugin/multialarmclock/fading_on'): + fade_in(main, exaile) +# thread.start_new(fade_in, (main, exaile)) + + if settings.get_option('plugin/multialarmclock/restart_playlist_on'): + logger.debug('try to restart playlist') if player.QUEUE.current_playlist: - player.QUEUE.current_playlist.set_current_pos(0) + player.QUEUE.current_playlist.set_current_position(-1) else: player.QUEUE.set_current_playlist(exaile.gui.main.get_selected_page()) @@ -389,64 +419,55 @@ ###><><><### Plugin Handling Functions ###><><><### -def __enb(eventname, exaile, nothing): - glib.idle_add(_enable, exaile) - def enable(exaile): - + '''Called by exaile to enable plugin''' if exaile.loading: - event.add_callback(__enb,'exaile_loaded') + logger.debug('waitin for loaded event') + event.add_callback(_enable,'exaile_loaded') else: - __enb(None, exaile, None) - -def _enable(exaile): - ''' - Called when plugin is loaded. Start timer and load previously saved alarms. - ''' - global TIMER_ID, MENU_ITEM, ALARM_CLOCK_MAIN - ALARM_CLOCK_MAIN = main = AlarmClock(exaile) + _enable(None, exaile, None) + + +@idle_add +def _enable(stuff, exaile, junk): + ''' + Enable plugin. Start timer and create class. + ''' + logger.debug('_enable called') + global TIMER_ID, MENU_ITEM, ALARM_CLOCK_MAIN, MY_BUILDER + + if ALARM_CLOCK_MAIN is None: + ALARM_CLOCK_MAIN = AlarmClock(exaile) + + main = ALARM_CLOCK_MAIN + + if MY_BUILDER is not None: + #'''Since I don't know if init() or enable() will be called first, save builder and setup UI if enabled() was already called.''' + main.init_ui(MY_BUILDER) TIMER_ID = glib.timeout_add_seconds(5, check_alarms, main, exaile) - MENU_ITEM = gtk.MenuItem(_('Multi-Alarm Clock')) - MENU_ITEM.connect('activate', main.show_ui, exaile) - exaile.gui.builder.get_object('tools_menu').append(MENU_ITEM) - MENU_ITEM.show() def disable(exaile): ''' - Called when plugin is unloaded. Stop timer, destroy main window if it exists, and save current alarms. + Called when plugin is unloaded. Stop timer. ''' - global TIMER_ID, MENU_ITEM, ALARM_CLOCK_MAIN + global TIMER_ID, MENU_ITEM, ALARM_CLOCK_MAIN, MY_BUILDER # Cleanup - if ALARM_CLOCK_MAIN.window: - ALARM_CLOCK_MAIN.window.destroy() - -# if main: - #main.save_list() # unnecessary -# main = None - if TIMER_ID is not None: glib.source_remove(TIMER_ID) TIMER_ID = None - if MENU_ITEM: - MENU_ITEM.hide() - MENU_ITEM.destroy() - MENU_ITEM = None - - -#def configure(): -# print 'no configure' - # If this is clicked before plugin is initialized, define globals (so alarms can still be created) - #global exaile, SETTINGS - #if exaile is None: - #exaile = APP - #SETTINGS = exaile.settings - # Show Window - #main.show_ui() + if ALARM_CLOCK_MAIN is not None: + ALARM_CLOCK_MAIN = None + +# disable/enable doesn't re-call init(), so if we scrap and re-create the class, we wont' get our gtk.Builder back, and there will +# be a disconnect between the Alarm class and the UI in the prefs page +# NOTE: reloading the plugin from prefs page (DEBUG MODE) breaks this anyway +# if MY_BUILDER is not None: +# MY_BUILDER = None === removed file 'plugins/multialarmclock/alarmclk.ui' --- plugins/multialarmclock/alarmclk.ui 2010-04-01 19:04:19 +0000 +++ plugins/multialarmclock/alarmclk.ui 1970-01-01 00:00:00 +0000 @@ -1,659 +0,0 @@ - - - - - - 100 - 1 - 10 - 10 - - - 100 - 1 - 10 - 10 - - - 100 - 1 - 10 - 10 - - - 100 - 1 - 10 - 10 - - - 9 - 23 - 1 - 1 - 1 - - - 69 - 1 - 10 - 10 - - - - - - True - - - True - - - True - True - False - False - False - - - 0 - - - - - True - - - gtk-add - True - True - True - True - - - - False - False - 0 - - - - - gtk-edit - True - True - True - True - - - - False - False - 1 - - - - - gtk-remove - True - True - True - True - - - - False - False - 2 - - - - - False - 1 - - - - - 0 - - - - - True - - - True - - - True - Alarm: - - - False - 0 - - - - - True - Name - Time - - - 1 - - - - - False - 0 - - - - - True - - - Enabled - True - True - False - True - - - - False - False - 0 - - - - - Restart playlist - True - True - False - True - - - - False - False - 1 - - - - - False - 1 - - - - - True - 0 - none - - - True - 12 - - - True - - - True - - - Enable fading - True - True - False - True - - - - 0 - - - - - - - - 0 - - - - - True - - - True - Minimum volume: - - - False - 2 - 0 - - - - - True - True - adjustment1 - True - - - - 1 - - - - - 1 - - - - - True - - - True - Maximum volume: - - - False - 0 - - - - - True - True - adjustment2 - True - - - - 1 - - - - - 2 - - - - - True - - - True - Increment: - - - False - 23 - 0 - - - - - True - True - adjustment3 - True - - - - 1 - - - - - 3 - - - - - True - - - True - Time per increment: - - - False - 16 - 0 - - - - - True - True - adjustment4 - True - - - - 1 - - - - - 4 - - - - - - - - - True - Fading: - True - - - - - 2 - - - - - 1 - - - - - - - 5 - center-on-parent - dialog - False - - - True - vertical - 2 - - - True - vertical - 6 - - - True - 6 - - - True - Alarm name: - - - False - False - 0 - - - - - True - True - - - 1 - - - - - 0 - - - - - True - 6 - - - True - Alarm time: - - - False - False - 0 - - - - - True - True - 1 - adjustment5 - True - - - False - False - 1 - - - - - True - : - - - False - False - 2 - - - - - True - True - adjustment6 - - - False - False - 3 - - - - - False - False - 1 - - - - - True - 0 - none - - - True - 0 - 0 - - - True - vertical - - - Monday - True - True - False - True - - - False - False - 0 - - - - - Tuesday - True - True - False - True - - - False - False - 1 - - - - - Wednesday - True - True - False - True - - - False - False - 2 - - - - - Thursday - True - True - False - True - - - False - False - 3 - - - - - Friday - True - True - False - True - - - False - False - 4 - - - - - Saturday - True - True - False - True - - - False - False - 5 - - - - - Sunday - True - True - False - True - - - False - False - 6 - - - - - - - - - True - <b>Alarm Days</b> - True - - - - - False - 2 - - - - - False - False - 1 - - - - - True - end - - - gtk-cancel - True - True - True - True - - - False - False - 0 - - - - - gtk-ok - True - True - True - True - - - False - False - 1 - - - - - False - end - 0 - - - - - - CancelButton - OkButton - - - === added file 'plugins/multialarmclock/cellrenderers.py' --- plugins/multialarmclock/cellrenderers.py 1970-01-01 00:00:00 +0000 +++ plugins/multialarmclock/cellrenderers.py 2010-09-15 23:30:03 +0000 @@ -0,0 +1,141 @@ +#!/usr/bin/python + +# Copyright (C) 2010 by Brian Parma +# +# 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import gtk +import gobject + +class CellRendererDays(gtk.CellRendererText): + '''Custom Cell Renderer for showing a ListView of 7 days with checkboxes, based off pygtk FAQ example''' + + __gtype_name__ = 'CellRendererDays' + __gproperties__ = { 'days':(object, 'days', 'List of enabled days', gobject.PARAM_READWRITE) } + __gsignals__ = { 'days-changed':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + (str, object))} + property_names = __gproperties__.keys() + + def __init__(self): + self.__gobject_init__() + self.model = gtk.ListStore(bool, str) + self.view = None + self.view_window = None + + for day in ['Sunday','Monday','Tuesday','Wednesday', 'Thursday', 'Friday', 'Saturday']: + self.model.append([True, day]) + + self.set_property('text','Edit me') + + + def _create_view(self, treeview): + '''Create the Window and View to display when editing''' + self.view_window = gtk.Window() + self.view_window.set_decorated(False) + self.view_window.set_property('skip-taskbar-hint', True) + + self.view = gtk.TreeView() + + self.view.set_model(self.model) + self.view.set_headers_visible(False) + + cr = gtk.CellRendererToggle() + cr.connect('toggled', self._toggle) + col = gtk.TreeViewColumn('Enabled', cr, active=0) + self.view.append_column(col) + + cr = gtk.CellRendererText() + col = gtk.TreeViewColumn('Day', cr, text=1) + self.view.append_column(col) + + # events + self.view.connect('focus-out-event', self._close) + self.view.connect('key-press-event', self._key_pressed) + + # should be automatic + self.view_window.set_modal(False) + self.view_window.set_transient_for(None) # cancel the modality of dialog + self.view_window.add(self.view) + + # necessary for getting the (width, height) of calendar_window + self.view.show() + self.view_window.realize() + + def do_set_property(self, pspec, value): + '''Set property overload''' + setattr(self, pspec.name, value) + + def do_get_property(self, pspec): + '''Get property overload''' + return getattr(self, pspec.name) + + def do_start_editing(self, event, treeview, path, background_area, cell_area, flags): + '''Called when user starts editing the cell''' + + if not self.get_property('editable'): + return + + # create window/view if it doesn't exist + if not self.view_window: + self._create_view(treeview) + else: + self.view_window.show() + + # set display to reflect 'days' property + for i,row in enumerate(self.model): + row[0] = self.days[i] + + + # position the popup below the edited cell (and try hard to keep the popup within the toplevel window) + (tree_x, tree_y) = treeview.get_bin_window().get_origin() + (tree_w, tree_h) = treeview.window.get_geometry()[2:4] + (my_w, my_h) = self.view_window.window.get_geometry()[2:4] + x = tree_x + min(cell_area.x, tree_w - my_w + treeview.get_visible_rect().x) + y = tree_y + min(cell_area.y, tree_h - my_h + treeview.get_visible_rect().y) + self.view_window.move(x, y) + + # save the path so we can return it in _done, and we aren't using dialog so we can't block.... + self._path = path + + return None # don't return any editable, our gtk.Dialog did the work already + + def _done(self): + '''Called when we are done editing''' + days = [ row[0] for row in self.model ] + + if days != self.days: + self.emit('days-changed', self._path, days) + + self.view_window.hide() + + + def _key_pressed(self, view, event): + '''Key pressed event handler, finish editing on Return''' + # event == None for day selected via doubleclick + if not event or event.type == gtk.gdk.KEY_PRESS and gtk.gdk.keyval_name(event.keyval) == 'Return': + self._done() + return True + + def _toggle(self, cell, path): + '''Checkbox toggle event handler''' + active = self.model[path][0] + self.model[path][0] = not active + return True + + def _close(self, view, event): + '''Focus-out-event handler''' + self._done() + return True + === removed file 'plugins/multialarmclock/clock32.png' Binary files plugins/multialarmclock/clock32.png 2009-03-24 00:30:02 +0000 and plugins/multialarmclock/clock32.png 1970-01-01 00:00:00 +0000 differ === added directory 'plugins/multialarmclock/icons' === added file 'plugins/multialarmclock/icons/clock32.png' Binary files plugins/multialarmclock/icons/clock32.png 1970-01-01 00:00:00 +0000 and plugins/multialarmclock/icons/clock32.png 2010-09-15 23:30:03 +0000 differ === added file 'plugins/multialarmclock/macprefs.py' --- plugins/multialarmclock/macprefs.py 1970-01-01 00:00:00 +0000 +++ plugins/multialarmclock/macprefs.py 2010-09-15 23:30:03 +0000 @@ -0,0 +1,281 @@ +# Copyright (C) 2010 Brian Parma +# +# 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 1, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import os +from xlgui.preferences import widgets +from xlgui import icons +from xl import xdg +from xl.nls import gettext as _ + +name = _('Multi-Alarm Clock') +basedir = os.path.dirname(os.path.realpath(__file__)) +ui = os.path.join(basedir, "malrmclk.ui") + +icons.MANAGER.add_icon_name_from_directory('clock', + os.path.join(basedir, 'icons')) +icon = 'clock' + + +class FadingEnabledPreference(widgets.CheckPreference): + name = 'plugin/multialarmclock/fading_on' + default = True + +class RestartPlaylistPreference(widgets.CheckPreference): + name = 'plugin/multialarmclock/restart_playlist_on' + default = False + +class FadeMinVolumePreference(widgets.SpinPreference): + name = 'plugin/multialarmclock/fade_min_volume' + default = 0 + +class FadeMaxVolumePreference(widgets.SpinPreference): + name = 'plugin/multialarmclock/fade_max_volume' + default = 100 + +class FadeIncrementPreference(widgets.SpinPreference): + name = 'plugin/multialarmclock/fade_increment' + default = 1 + +class FadeTimePreference(widgets.SpinPreference): + name = 'plugin/multialarmclock/fade_time' + default = 30 + + +#class SelectionListPreference(widgets.Preference): +# """ +# A list allowing for enabling/disabling +# as well as reordering of items + +# Options: +# * items: list of :class:`SelectionListPreference.Item` objects +# * default: list of item ids +# """ +# class Item(object): +# """ +# Convenience class for preference item description +# """ +# def __init__(self, name, time, days, enabled): +# """ +# :param id: the unique identifier +# :type id: string +# :param title: the readable title +# :type title: string +# :param description: optional description of the item +# :type description: string +# :param fixed: whether the item should be removable +# :type fixed: bool +# """ +# self.__name = name +# self.__time = time +# self.__days = days +# self.__enabled = enabled + +# name = property(lambda self: self.__name) +# time = property(lambda self: self.__time) +# days = property(lambda self: self.__days) +# enabled = property(lambda self: self.__enabled) + +# def __init__(self, preferences, widget): +# self.model = gtk.ListStore( +# str, # 0: name +# str, # 1: time +# list, # 2: days +# bool # 3: enabled +## bool # 4: fixed +# ) +# +# for item in self.items: +# self.model.append([item.name, item.time, item.days, item.enabled]) + +## tree = gtk.TreeView(self.model) +# tree = widget +## tree.set_headers_visible(False) +# tree.set_rules_hint(True) +# tree.enable_model_drag_source( +# gtk.gdk.BUTTON1_MASK, +# [('GTK_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)], +# gtk.gdk.ACTION_MOVE +# ) +# tree.enable_model_drag_dest( +# [('GTK_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)], +# gtk.gdk.ACTION_MOVE +# ) +# tree.connect('row-activated', self.on_row_activated) +# tree.connect('key-press-event', self.on_key_press_event) +# tree.connect('drag-end', self.change) + +# toggle_renderer = gtk.CellRendererToggle() +# toggle_renderer.connect('toggled', self.on_toggled) +# enabled_column = gtk.TreeViewColumn('Enabled', toggle_renderer, active=3) +# enabled_column.set_cell_data_func(toggle_renderer, +# self.enabled_data_function) +# tree.append_column(enabled_column) + +# text_renderer = gtk.CellRendererText() +# text_renderer.props.ypad = 6 +# title_column = gtk.TreeViewColumn('Title', text_renderer, text=0) +# title_column.set_cell_data_func(text_renderer, +# self.title_data_function) +# tree.append_column(title_column) + +# text_renderer = gtk.CellRendererText() +# time_column = gtk.TreeViewColumn('Time', text_renderer, text=1) +# time_column.set_cell_data_func(text_renderer, +# self.time_data_function) +# tree.append_column(time_column) +# +# # days? +# +# # restart pl + +## scroll = gtk.ScrolledWindow() +## scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) +## scroll.set_shadow_type(gtk.SHADOW_IN) +## scroll.add(tree) + +## guiutil.gtk_widget_replace(widget, scroll) +# Preference.__init__(self, preferences, scroll) + +# def _get_dict(self): +# return [ {'name':row[0], +# 'time':row[1], +# 'days':row[2], +# 'enabled':row[3]} for row in self.model if row[3]] + +# def _get_value(self): +# """ +# Value to be stored in the settings +# """ +# return self._get_dict() + +# def _set_value(self): +# """ +# Updates the internal representation +# """ +# selected_items = settings.get_option(self.name, self.default) +# # Get list of available items +# available_items = [row[0] for row in self.model] + +# if not available_items: +# return + +# # Filter out invalid items +# selected_items = [item for item in selected_items \ +# if item in available_items] +# # Cut out unselected items +# unselected_items = [item for item in available_items \ +# if item not in selected_items] +# # Move unselected items to the end +# items = selected_items + unselected_items +# new_order = [available_items.index(item) for item in items] +# self.model.reorder(new_order) + +# # Disable unselected items +# for row in self.model: +# if row[0] in unselected_items and not row[4]: +# row[3] = False +# else: +# row[3] = True + +# def enabled_data_function(self, column, cell, model, iter): +# """ +# Prepares sensitivity +# of the enabled column +# """ +# path = model.get_path(iter) +# fixed = model[path][4] +# cell.props.sensitive = not fixed + +# def title_data_function(self, column, cell, model, iter): +# """ +# Prepares the markup to be +# used for the title column +# """ +# path = model.get_path(iter) +# title, description = model[path][1], model[path][2] + +# markup = '%s' % title + +# if description is not None: +# markup += '\n%s' % description + +# cell.props.markup = markup + +# def iter_prev(self, iter, model): +# """ +# Returns the previous iter +# Taken from PyGtk FAQ 13.51 +# """ +# path = model.get_path(iter) +# position = path[-1] + +# if position == 0: +# return None + +# prev_path = list(path)[:-1] +# prev_path.append(position - 1) +# prev = model.get_iter(tuple(prev_path)) + +# return prev + +# def on_row_activated(self, tree, path, column): +# """ +# Updates the enabled column +# """ +# model = tree.get_model() + +# if model[path][4]: +# return + +# enabled = not model[path][3] +# model[path][3] = enabled + +# def on_key_press_event(self, tree, event): +# """ +# Allows for reordering via keyboard (Alt+) +# """ +# if not event.state & gtk.gdk.MOD1_MASK: +# return + +# if event.keyval not in (gtk.keysyms.Up, gtk.keysyms.Down): +# return + +# model, selected_iter = tree.get_selection().get_selected() + +# if event.keyval == gtk.keysyms.Up: +# previous_iter = self.iter_prev(selected_iter, model) +# model.move_before(selected_iter, previous_iter) +# elif event.keyval == gtk.keysyms.Down: +# next_iter = model.iter_next(selected_iter) +# model.move_after(selected_iter, next_iter) + +# tree.scroll_to_cell(model.get_path(selected_iter)) + +# self.apply() + +# def on_toggled(self, cell, path): +# """ +# Updates the enabled column +# """ +# if self.model[path][4]: +# return + +# active = not cell.get_active() +# cell.set_active(active) +# self.model[path][3] = active + +# self.apply() + === added file 'plugins/multialarmclock/malrmclk.ui' --- plugins/multialarmclock/malrmclk.ui 1970-01-01 00:00:00 +0000 +++ plugins/multialarmclock/malrmclk.ui 2010-09-15 23:30:03 +0000 @@ -0,0 +1,372 @@ + + + + + + + + True + + + True + 0 + none + + + True + 12 + + + True + + + True + True + adjustment1 + adjustment2 + automatic + automatic + in + + + + + + 0 + + + + + True + 6 + True + end + + + Add + True + True + True + + + False + False + 0 + + + + + Remove + True + True + True + + + False + False + 1 + + + + + False + 6 + 1 + + + + + Restart playlist + True + True + False + True + + + False + 2 + + + + + + + + + True + <b>Alarms</b> + True + + + + + 0 + + + + + True + 0 + none + + + True + 12 + + + True + 5 + 2 + + + Enable + True + True + False + True + + + 2 + GTK_FILL + + + + + True + 0 + Minimum volume: + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Maximum volume: + + + 2 + 3 + GTK_FILL + + + + + True + 0 + Fade Increment: + + + 3 + 4 + GTK_FILL + + + + + True + 0 + Fade Time (s): + + + 4 + 5 + GTK_FILL + + + + + True + 10 + + + True + True + plugin/multialarmclock/fade_min_volume + 100 + False + + + 0 + + + + + True + True + 3 + + 3 + plugin/multialarmclock/fade_min_volume + True + + + False + 1 + + + + + 1 + 2 + 1 + 2 + 10 + + + + + True + 10 + + + True + True + plugin/multialarmclock/fade_max_volume + False + + + 0 + + + + + True + True + 3 + + 3 + plugin/multialarmclock/fade_max_volume + True + + + False + 1 + + + + + 1 + 2 + 2 + 3 + 10 + + + + + True + + + True + True + + 3 + plugin/multialarmclock/fade_increment + + + False + 10 + 0 + + + + + + + + 1 + 2 + 3 + 4 + + + + + True + + + True + True + 6 + + 3 + adjustment3 + True + + + False + 10 + 0 + + + + + + + + 1 + 2 + 4 + 5 + + + + + + + + + True + <b>Fading</b> + True + + + + + False + 1 + + + + + + + 100 + 1 + 10 + + + 100 + 100 + 1 + 10 + + + 100 + 1 + 10 + + + 100 + 1 + 10 + 10 + + + 100 + 1 + 10 + 10 + + + 30 + 9999999 + 1 + 10 + + # Begin bundle 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 yRZA