#!/usr/bin/env python

#    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 3 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, see <http://www.gnu.org/licenses/>.

# VolumeControlScreenlet (c) 2007 DeathCarrot <jsk105@ecs.soton.ac.uk>
# 
# A basic screenlet for controlling audio volume.
# Use the mousewheel to raise and lower the volume or middle click to toggle mute.
#
# TODO:
#   Add checks to make sure cmdValue actually returns a number
#   .. or think of a more versatile way of getting the volume data
#   Move size/offset information into theme-specific config files
#   Make some more themes


import screenlets
from screenlets.options import StringOption, IntOption
import pygtk
pygtk.require('2.0')
import gtk
import cairo
import commands
import os

# use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

def tdoc(obj):
	obj.__doc__ = _(obj.__doc__)
	return obj

@tdoc
class VolumeControlScreenlet(screenlets.Screenlet):
	"""A basic screenlet for controlling audio volume"""

	# default meta-info for Screenlets
	__name__ = 'VolumeControlScreenlet'
	__version__ = '0.1.9'
	__author__ = 'DeathCarrot'
	__desc__ = _('A basic screenlet for controlling the volume.'
		'Use the mousewheel to raise and lower the volume,'
		'middle click to activate/deactivate mute or '
		'click on the bar to set the volume level.')
	
	# constants
	__cmdSet = 'amixer -c %s sset %s %s on'
	__cmdMute = 'amixer -c %s sset %s 0 off'
	__cmdGet = 'amixer  -c %s sget %s | grep dB'

	# constructor
	def __init__(self, **keyword_args):
		# call super
		screenlets.Screenlet.__init__(self, width=100, height=100, resize_on_scroll=False, 
			**keyword_args)
		
		# internals
		self.__cardList = []
		self.__currentVol = []
		self.__maxVol = 1
		self.__warned_user = False
		
		self.cardNo = "0" # Number of card
		
		# theme options
		self.bar_x = 59
		self.bar_y = 7
		self.bar_width = 12
		self.bar_height = 75
		
		# connect mousewheel scroll event
		self.window.connect("scroll-event", self.on_scroll)
		
		# set theme
		self.theme_name = "cone"
		
		# add menuitem for mixer
		self.add_menuitem("mixer",_("Launch Mixer"), self.run_mixer)
		self.add_default_menuitems()
		
		cList = commands.getoutput("cat /proc/asound/cards").splitlines()  	#convert /proc/asound/cards to a list
		for line in cList:			#":" means that we've got a sound card name
			if line[20:21]==":":	# append it to cardList
				self.__cardList.append(line[22:])
		
		# add options group
		self.add_options_group(_('Device'), _('Settings for specifying which device and control to use'))
		
		# add options
		# Name of card from /proc/asound/cards
		self.add_option(StringOption(_('Device'),	# group name
			'cardName',					# attribute-name
			"",							# default-value
			_('Card'),					    # widget-label
			_('Which card should be used'),# description
			self.__cardList),			# list contents
			self.on_control_update		# callback
			)
		
		ctlList = [ ' '.join(l.split()[3:]) for l in commands.getoutput("amixer -c %s scontrols" % (self.cardNo)).splitlines() ]
		
		self.add_option(StringOption(_('Device'), 'control', _('Master'), _('Control'), 
			_('Which control should be utilised for your device'), ctlList), self.on_control_update)
		self.add_option(IntOption(_('Device'), 'step', 2, _('Scroll Step'),
			_('How much the volume changes on each mouse wheel click'), 0,	100))
		self.add_option(StringOption(_('Device'), 'mixer', 'xfce4-mixer', _('Mixer Command'), _('The command to be run when mixer is launched')))

	def on_control_update(self, option, option2):
		cList = commands.getoutput("cat /proc/asound/cards").splitlines()  
		for line in cList:
			if line[22:]==self.cardName:			
				self.cardNo=line[1:2]
		# find the maximum volume and update
		self.__maxVol = int(commands.getoutput("amixer -c %s sget %s | awk '/^  Limits/{print $5}'" %(self.cardNo, self.control)))
		print _("Max vol: ") + str(self.__maxVol) + "; "+self.control
		self.updateBar()
	
	def on_init(self):
		self.on_control_update(self.control, self.control)
		
	def on_mouse_down(self, event):
		if event.button == 2:
			# mute on middle click
			# KjellBraden: I've no real idea about unmuting, but that can be done with
			# the scrollwheel anyway
		    commands.getoutput(self.__cmdMute % (self.cardNo, self.control))
		    self.updateBar()
		elif event.button == 1 and \
			event.x > self.scale*self.bar_x and \
			event.x < self.scale*(self.bar_x+self.bar_width) and \
			event.y > self.scale*self.bar_y and \
			event.y < self.scale*(self.bar_y+self.bar_height):
			new_vol = (self.bar_height*self.scale + self.bar_y*self.scale - event.y)/(self.bar_height*self.scale) * self.__maxVol
			commands.getoutput(self.__cmdSet % (self.cardNo, self.control, str(new_vol)))
			self.updateBar()
		return False
	
	def on_scroll(self, widget, event):
		if event.direction == gtk.gdk.SCROLL_UP:
			commands.getoutput(self.__cmdSet % (self.cardNo, self.control, str(self.step)+"+"))
			self.updateBar()
		elif event.direction == gtk.gdk.SCROLL_DOWN:
			commands.getoutput(self.__cmdSet % (self.cardNo, self.control, str(self.step)+"-"))
			self.updateBar()
		return False

	def run_mixer(self, option, option2):
		os.spawnlp(os.P_NOWAIT,self.mixer)

	# read volume value and redraw bar
	def updateBar(self):
		#get current volume value
		mixer_info = commands.getoutput(self.__cmdGet % (self.cardNo,self.control)).splitlines()
		#assert(len(mixer_info) > 0)
		if len(mixer_info) > 0 :
			self.__currentVol = []
			for line in mixer_info:
				info = ':'.join(line.split(':')[1:]).split()
				vol_percent = float(info[2][1:-2])/100 # truncate surrounding [ and %]
			# append the volume to __curentVol list, with 1 as maximum and 0.001 as minimum:
			self.__currentVol.append(vol_percent > 1 and 1 or (vol_percent <= 0 and 0.001 or vol_percent))
			self.redraw_canvas()
		elif not self.__warned_user:
			screenlets.show_message(self, _("Unsupported volume card. All functionality is disabled."), _("Warning"))
			self.__warned_user = True
		
	def on_draw(self, ctx):
		# if theme is loaded
		if self.theme:
			ctx.scale(self.scale, self.scale)
			# draw main part
			self.theme['vol_base.svg'].render_cairo(ctx)
			# save old state and mask bar
			ctx.save()
			s_width = self.bar_width / (len(self.__currentVol) or 1)
			for i in range(len(self.__currentVol)):
				v = self.__currentVol[i]
				ctx.rectangle(self.bar_x + i*s_width, (self.bar_height*(1-v))+self.bar_y,s_width, self.bar_height)
			ctx.clip()
			# draw bar
			self.theme['vol_bar.svg'].render_cairo(ctx)
			ctx.restore()

	def on_draw_shape(self, ctx):
		self.on_draw(ctx)

# If the program is run directly or passed as an argument to the python
# interpreter then create a Screenlet instance and show it
if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(VolumeControlScreenlet)
