#!/usr/bin/python2

#  StocksScreenlet (c) Patrik Kullman 2007 <patrik@yes.nu>

#    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/>.
#
# INFO:
# - Retrieve stock-information from finance.yahoo.com
# - Info button opens a more detailed view on finance.yahoo.com in webbrowser
#
# TODO:
# - make web browser gain focus and leave widget-mode when info-button is clicked
#
# CHANGELOG:
# v0.2:
# - added fault tolerance when not being able to retrieve data (print it in the screenlet, retry every 30 seconds until success)
# v0.2.4:
# - rewrote stock name and statistic grabbing so that the screenlet can handle indexes as well as stocks.

import screenlets
from screenlets.options import BoolOption
from screenlets.options import IntOption
from screenlets.options import StringOption
import cairo
import gtk
import pango
from urllib2 import urlopen
from urllib2 import URLError
from os import system
import gobject
import re

# use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

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

@tdoc
class StocksScreenlet(screenlets.Screenlet):
	"""Retrieve stock-information from finance.yahoo.com"""
	
 
	# default meta-info for Screenlets
	__name__ = 'StocksScreenlet'
	__version__ = '0.2.4++'
	__author__ = 'Patrik Kullman (modified by Pedro Huerta) updated by Shelby Spencer'
	__desc__ = __doc__

	# internals
	__word = {"word": _("Fetching stock..."), "desc": _("Please stand by...")}
	__stockdata = {'name': _("Not updated"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
	__timeout = None
	__offline_timer = None

	# editable options and defaults
	update_interval = 3600 # every hour
	symbol = '^DJI'
	stockName = 'Dow Jones Industrial Average'
	use_percentage = False
	p_layout = None
	
	# constructor
	def __init__(self, **keyword_args):
		# call super
		screenlets.Screenlet.__init__(self, width=200, height=100, **keyword_args)
		# set theme
		self.theme_name = "default"
		# add option groups
		self.fetch_stocks()
		self.update_interval = self.update_interval
		self.add_options_group(_('Stocks'), _('Stocks settings'))
		self.add_option(IntOption(_('Stocks'),
			'update_interval', 						# attribute-name
			self.update_interval,						# default-value
			_('Update interval (seconds)'), 						# widget-label
			_('Specify number of seconds between re-fetching stock data'), min=60, max=86400	# description
			))
		self.add_option(BoolOption(_('Stocks'),
			'use_percentage', 						# attribute-name
			self.use_percentage,						# default-value
			_('Use % variation'),						# widget-label
			_('Show % variation instead of absolute change'),	# description
			))
		self.add_option(StringOption(_('Stocks'),
			'symbol', 						# attribute-name
			self.symbol,						# default-value
			_('Stock symbol'), 						# widget-label
			_('The stock symbol of the company'),	# description
			))

	def on_init (self):
		# add default menu items
		self.add_default_menuitems()

	def __setattr__(self, name, value):
		# call Screenlet.__setattr__ in baseclass (ESSENTIAL!!!!)
		screenlets.Screenlet.__setattr__(self, name, value)
		# redraw the canvas when server-info is changed since a server host/ip addition/removal will change the size of the screenlet
		if name == "update_interval":
			if value > 0:
				self.__dict__['update_interval'] = value
				if self.__timeout:
					gobject.source_remove(self.__timeout)
				self.__timeout = gobject.timeout_add(value * 1000, self.update)
			else:
				# TODO: raise exception!!!
				pass
		if name == "symbol":
			self.fetch_stock_name()

		if name == "use_percentage":
			self.fetch_stocks()

	def update(self):
		gobject.idle_add(self.fetch_stocks)
		return True

	def on_draw(self, ctx):
		# scaling above 500% is very slow and might brake it (why?)
		if self.scale > 5:
			self.scale = 5
		# if theme is loaded
		if self.theme:
			# find out how many servers that are configured and force a update_shape() when they're changed from the last draw.
			# apparently the settings doesn't get loaded until the widget is first displayed, and no update_shape is performed at
			# that point
			# make sure that the background covers all the icons
			ctx.save()
			ctx.scale(self.scale, self.scale)
			ctx.translate(0, 1)
			self.theme.render(ctx, 'background')
			ctx.restore()

			ctx.save()
			ctx.translate(5 * self.scale, 75 * self.scale) #info button
			ctx.scale(self.scale * 0.5, self.scale * 0.5)
			self.theme.render(ctx, 'info')
			ctx.restore()

			ctx.save()
			ctx.scale(self.scale, self.scale)
			self.draw_text(ctx, '<b>' + self.__stockdata['name'].replace('&', '&amp;')[:18] + '</b>', 10, 5, 12)
			if self.__stockdata['change'] == "N/A":
				change_color = "white"
				icon = "level"
			else:
				stockchange = float(self.__stockdata['change'])
				if (stockchange == 0):
					change_color = "white"
					icon = "level"
				elif (stockchange < 0):
					change_color = "red"
					icon = "down"
				elif (stockchange > 0):
					change_color = "green"
					icon = "up"
			if self.use_percentage:
				self.draw_text(ctx, _("Change: <span foreground='") + change_color + "'>" + str(self.__stockdata['percent_change']) + "%</span>", 8, 38, 10)
			else:
				self.draw_text(ctx, _("Change: <span foreground='") + change_color + "'>" + str(self.__stockdata['change']) + "</span>", 10, 38, 10)
			self.draw_text(ctx, _("Last trade: ") + str(self.__stockdata['last_trade']), 8, 55, 10)
			ctx.restore()

			ctx.save()
			ctx.scale(self.scale, self.scale)
			ctx.translate(147, 40) #arrow graph
			ctx.scale(0.5, 0.5)
			self.theme.render(ctx, icon)
			ctx.restore()

			ctx.save()
			ctx.scale(self.scale, self.scale)
			ctx.translate(0, 0)
			self.theme.render(ctx, 'glass')
			ctx.restore()

	def on_mouse_down(self, event):
		if event.button != 1 or event.type != gtk.gdk.BUTTON_PRESS:
			return False
		# recalculate cursor position
		x = event.x / self.scale
		y = event.y / self.scale
		if 5 < x < 25 and  95 > y > 75:
			self.open_info()
		if 65 < x and x < 120 and 38 < y and y < 50:
			print x, y
			self.__setattr__('use_percentage', not self.use_percentage)
			self.redraw_canvas()

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

	def draw_text(self, ctx, value, x, y, size):
		# stolen from CalcScreenlet ;)
		ctx.save()
		ctx.translate(x, y)
		if self.p_layout == None :
	
			self.p_layout = ctx.create_layout()
		else:
		
			ctx.update_layout(self.p_layout)
		p_fdesc = pango.FontDescription()
		p_fdesc.set_family_static("Sans")
		p_fdesc.set_size(size * pango.SCALE)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_width(180 * pango.SCALE)
		self.p_layout.set_alignment(pango.ALIGN_LEFT)
		self.p_layout.set_markup(value)
		ctx.set_source_rgba(1, 1, 1, 0.8)
		ctx.show_layout(self.p_layout)
		self.p_layout.set_alignment(pango.ALIGN_CENTER)
		ctx.restore()

	def fetch_stock_name(self):
		error = False
	
		try:
			page = urlopen('http://d.yimg.com/aq/autoc?query=' + self.symbol + '&region=US&lang=en-US&callback=YAHOO.util.ScriptNodeDataSource.callbacks')
			dirtyText = page.read().decode('utf-8')
			page.close()
		
			cleanCode = re.compile(r'{[^{]*?\"symbol\":\s?\"' + re.escape(self.symbol.upper()) + '\".*?}', re.I | re.M | re.S)
			result = cleanCode.findall(dirtyText)
			
			if len(result) == 1:
				getStockName = re.compile(r'\"name\":\s?\"(.*?)\"', re.I | re.M | re.S)
				result = getStockName.findall(result[0])
				
				if len(result) == 1:
					self.stockName = result[0]
					self.fetch_stocks()
				else:
					error = True
					stockdata = {'name': _("Wrong symbol"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
			else:
				stockdata = {'name': _("Wrong symbol"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
				error = True
					
		except URLError:
			stockdata = {'name': _("Connection Error"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
			error = True
			
		if error:		
			self.__stockdata = stockdata
			stockdata = None
			self.redraw_canvas()
		
	def fetch_stocks(self):
		if self.__offline_timer:
			gobject.source_remove(self.__offline_timer)
		try:
			page = urlopen('https://query.yahooapis.com/v1/public/yql?q=select%20Change%2CLastTradePriceOnly%20from%20yahoo.finance.quote%20where%20symbol%20%3D%20%22' + self.symbol + '%22&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=')

			dirtyText = page.read(300).decode('utf-8')
			page.close()
			errorFlag = False			
			try:
				pattern = re.compile("\"Change\":\"([-+]?[0-9]*\.?[0-9]+)\",\"LastTradePriceOnly\":\"([-+]?[0-9]*\.?[0-9]+)\"")
				values = pattern.search(dirtyText).groups()		
			except:	
				stockdata = {'name': _("Wrong symbol"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
				errorFlag = True
			
			if not errorFlag:
				stockdata = {}
				last_trade = float(values[1])
				change = float(values[0])
				stockdata['name'] = self.stockName
				stockdata['last_trade'] = "%.2f" % last_trade
				stockdata['change'] = "%.2f" % change
				stockdata['percent_change'] = "%.2f" % (change/(last_trade-change)*100)
		except URLError:
			stockdata = {'name': _("Connection Error"), 'last_trade': 0.00, 'change': 0.00, 'percent_change': 0.00}
			self.__offline_timer = gobject.timeout_add(30 * 1000, self.update)
		self.__stockdata = stockdata
		stockdata = None
		self.redraw_canvas()

	def open_info(self):
		system("xdg-open 'http://finance.yahoo.com/q?s=" + self.symbol + "'")

#!/usr/bin/python2
# interpreter then create a Screenlet instance and show it
if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(StocksScreenlet)

