#!/usr/bin/python

# This is a part of the external Translator (formally doCkranslator) applet for Cairo-Dock
#
# Author: Eduardo Mucelli Rezende Oliveira
# E-mail: edumucelli@gmail.com or eduardom@dcc.ufmg.br
#
# 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.

# This applet provides a translator tool using the Google Translator service
#    Translator (formally doCkranslator) translates from lots of languages to lots of languages
#    First it is necessary to choose the languages using one of the following ways:
#        (I) Scroll up/down over the icon to choose the destination language
#        (II) Right-click on the icon -> "To", or "From"
#        (III) Right-click on the icon -> Translator -> Configuration
#    To translate you can do it using one of the following ways:
#        (I) Left-click on the icon; type your text and press Enter
#        (II) Select any text in any place, and middle click on the icon
#    The plugin also provides useful keyboard shortcuts (requires Python-Xlib, or Cairo-dock 2.2.0)
#        If you want to translate something you are reading in the foreign language you chose "From":
#        * Press Ctrl + Alt + R, type the text, and press Enter 
#        If you are writing something in your native language you chose "To":
#        * Press Ctrl + Alt + W, type the text, and press Enter
#    Translated text will be shown as a dialog and be available in the clipboard, just press Ctrl+v to have it

import os, webbrowser

try:
	import gtk
except:
	from gi.repository import Gtk
	from gi.repository import Gdk

try:  # python3
	from configparser import RawConfigParser
	from urllib.request import FancyURLopener
	from html.parser import HTMLParser
	strict=False
except:  # python 2
	from ConfigParser import RawConfigParser
	from urllib import FancyURLopener
	from HTMLParser import HTMLParser
	strict=True

from util import log

from CDApplet import CDApplet, _

class AgentOpener(FancyURLopener):
	"""Masked user-agent otherwise the access would be forbidden"""
	version = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11'

class TranslatorParser(HTMLParser):
	def reset(self):                              
		HTMLParser.reset(self)
		self.inside_result = False
		self.translated_content = ""
	
	# sentences
	def handle_starttag(self, tag, attrs):
		if tag == 'span':
			for name, value in attrs:
				if name == "id" and value == "result_box":  # <span id="result_box">translated text</span>
					self.inside_result = True
	
	def handle_endtag(self, tag):
		if tag == 'span':
			self.inside_result = False
	
	def handle_data(self, text):
		# sentences
		if self.inside_result:                                                 # we're inside the tag ...
			if self.translated_content != "":  # the data can be separated in several parts if they contain "'"
				self.translated_content += "'"
			self.translated_content += text                                          # ... grab the text!

	def parse(self, page):
		if strict: # python 2
			self.feed(page.decode('utf-8')) # feed the parser with the page's html
		else:
			self.feed(str(page)) ## workaround for python3
		self.close()                                                              # cya soon!


class Interface:                                                              # TODO: Create an interface to support Bing Translator
	""" Create a interface between the Applet and Parser
		This module receives, from the Applet's user, the text to be translated and
		access the parser to get the context of the Google Translator for this text"""
	def __init__(self, text_to_be_translated):
		self.text_to_be_translated = text_to_be_translated
	
	def translate_it(self, source, destination):
		if not strict:
			parser = TranslatorParser(strict=False)  # 'strict=False' is required to avoid an EOF error while parsing
		else:
			parser = TranslatorParser()  # in python2, there is no 'strict' parameter
		opener = AgentOpener()                                                          # opens the web connection with masked user-agent
		url = "http://translate.google.com/?sl=%s&tl=%s&q=%s" % (source, destination, self.adjust(self.text_to_be_translated))
		try:
			page = opener.open(url,)                                                       # get the HTML
		except IOError:
			log ("Problem to open the remote translator, check the text to be translated")
		else:
			parser.parse(str(page.read()))                                                # feed the parser to get the specific content: translated text
			page.close()                                                                  # lets close the page connection
			self.text_to_be_translated = parser.translated_content  # from the parser, we get the translated content
		return self.text_to_be_translated

	def adjust(self, text):
		"""Clean up the text removing spaces, linebreaks, and encodes it with utf-8"""
		log ("Text to be translated before adjust: %s" % (self.text_to_be_translated))
		self.text_to_be_translated = self.text_to_be_translated.strip().replace(os.linesep, ' ').replace(' ', '%20').encode('utf-8')
		log ("Text to be translated after adjust: %s" % (self.text_to_be_translated))
		return self.text_to_be_translated.decode('utf-8')

class Language:
	def __init__(self, name, abbrv):
		self.name = bytes.decode(name)
		self.abbrv = bytes.decode(abbrv)

class Applet(CDApplet):
	DIALOG_QUERY = 0
	DIALOG_RESULT = 1
	
	def __init__(self):
		# define internal variables
		self.translated_text = ""
		self.text_to_be_translated = ""
		self.source = None                                              # get it from configuration file
		self.languages = []                                               # list of possible source languages
		self.destination = None                                         # get it from configuration file
		# self.favorites = []                                           # TODO list of favorite languages
		self.new_translation_key = 0                                    # there are 3 buttons, cancel = 2, new = 1 ...
		self.edit_translation_key = 1                                   # ... and edit
		self.dialog_type = self.DIALOG_QUERY	                                          # dialog we are displaying to the user
		
		(self.destination_language_menu_id, self.source_language_menu_id,
		self.switch_languages_menu_id, self.clipboard_menu_id) = list(range(1000, 5000, 1000)) # 1000, 2000, 3000, 4000
		
		# call high-level init
		CDApplet.__init__(self)
	
	##### private methods #####
	
	# read the list of languages from .languages file
	def read_languages_file(self):
		"""Read the languages file formated as Name<space>Abbreviation, e.g, Portuguese pt"""
		f = open('.languages', "rb")
		for line in f:
			splited = line.split()                                      # split the line by space token
			name, abbrv = splited[0], splited[1]
			self.languages.append(Language(name, abbrv))                  # e.g, Language("English", "en")

  # ask the user for the text to be translated
	def ask_text(self, default=""):
		label = "Translate from <b>%s</b> to <b>%s</b>:" % (self.source.name, self.destination.name)
		dialog_attributes = {
			'message'    : label,
			'use-markup' : True,
			'buttons'    : 'ok;cancel' }
		widget_attributes = {
			'widget-type'   : 'text-entry',
			'initial-value' : default,
			'multi-lines'   : self.config['dialog editable'] }
		self.icon.PopupDialog (dialog_attributes, widget_attributes)
		self.dialog_type = self.DIALOG_QUERY

	# Clipboard operations
	def set_to_clipboard(self, sentence):
		try:
			clipboard = gtk.clipboard_get()                               # get the clipboard
			clipboard.set_text(sentence)
		except:
			clipboard = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
			clipboard.set_text(sentence, -1)

	def get_from_clipboard(self):
		try:
			clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY)                           # selected with the mouse
			if not clipboard:                                             # nothing was selected with mouse
				clipboard = gtk.clipboard_get()                             # checks for something copied to the clipboard
		except:
			clipboard = Gtk.Clipboard.get(Gdk.SELECTION_PRIMARY)
			if not clipboard:                                             # nothing was selected with mouse
				clipboard = Gtk.Clipboard.get()                             # checks for something copied to the clipboard
		return clipboard.wait_for_text()

	# Icon information
	def inform_start_of_waiting_process(self):
		self.icon.SetQuickInfo("...")

	def inform_end_of_waiting_process(self):
		self.icon.SetQuickInfo(self.destination.abbrv.capitalize())   # set back the usual quick-info

	def inform_current_destination_language(self):
		self.icon.SetQuickInfo(self.destination.abbrv.capitalize())
		if self.config['default title'] == "":
			self.icon.SetLabel("%s -> %s" % (self.source.name, self.destination.name))
	
	def inform_current_source_language(self):
		if self.config['default title'] == "":
			self.icon.SetLabel("%s -> %s" % (self.source.name, self.destination.name))

	# Translation
	def translate(self, sentence, source, destination):
		# Translate URL
		if sentence.startswith(("http://", "https://", "www.")):                      # barely checking if user is trying to translate a web page
			url = "http://translate.google.com/translate?sl=%s&tl=%s&u=%s" % (source, destination, sentence)
			webbrowser.open(url)
			log("Translated URL %s" % sentence)
		else:                                                                         # Translate one word, or sentence
			self.inform_start_of_waiting_process()
			
			interface = Interface(sentence)                                             # feeds the translator with the sentence to be translated
			translated = interface.translate_it(source, destination)                    # translated text
			log ("Translated sentence %s" % translated)
			
			dialog_attributes = {
				'use-markup' : True,
				'buttons'    : 'gtk-clear;gtk-refresh;gtk-cancel',
				'time-length': self.config['dialog time'] + len(translated) / 20 }
			if self.config['dialog editable']:
				dialog_attributes['message'] = "Translated from <b>%s</b> to <b>%s</b>:" % (self.source.name, self.destination.name)
				widget_attributes = {
					'widget-type'   : 'text-entry',
					'initial-value' : translated,
					'multi-lines'   : True }
			else:
				dialog_attributes['message'] = translated
				widget_attributes = {}
			self.icon.PopupDialog (dialog_attributes, widget_attributes )
			self.dialog_type = self.DIALOG_RESULT
			
			self.set_to_clipboard(translated)                                           # set the result to the clipboard
			self.inform_end_of_waiting_process()

	def set_read_mode(self):
		self.source = self.languages[self.config['slang']]
		self.destination = self.languages[self.config['dlang']]

	def set_write_mode(self):
		self.source = self.languages[self.config['dlang']]
		self.destination = self.languages[self.config['slang']]
	
	# Applet methods
	
	def get_config(self,keyfile):
		self.config['slang']			      = keyfile.getint('Configuration', 'source')               # get the source language index
		self.config['dlang'] 			      = keyfile.getint('Configuration', 'destiny')              # get the destination language index
		self.config['shortkey_from'] 	  = keyfile.get('Configuration', 'shortkey_from')           # read
		self.config['shortkey_to'] 		  = keyfile.get('Configuration', 'shortkey_to')             # write
		self.config['dialog time'] 		  = keyfile.getint('Configuration', 'dialog time')          # time in seconds the dialog window will be active
		self.config['dialog editable'] 	= keyfile.getboolean('Configuration', 'dialog editable')  # whether the user can edit the result
		self.config['default title'] 	  = keyfile.get('Icon', 'name')                             # icon's label set by the user.

	def begin(self):
		# parse the .languages file to get the available languages (in self.languages)
		self.read_languages_file()
		
		# set the initial source and destination.
		self.source = self.languages[self.config['slang']]
		self.destination = self.languages[self.config['dlang']]                          # first set the destination language ...
		self.inform_current_destination_language()                                          # ... and show it

		# bind the shortkeys.
		self.icon.BindShortkey([self.config['shortkey_from'], self.config['shortkey_to']])  # reading/writing
		
	def reload(self):
		# update the current source and destination
		self.source = self.languages[self.config['slang']]
		self.destination = self.languages[self.config['dlang']]                          # first set the destination language ...
		
		self.inform_current_destination_language()                                          # ... and show it
		
		# re-bind the shortkeys with their new values.
		self.icon.BindShortkey([self.config['shortkey_from'], self.config['shortkey_to']])  # reading/writing
	
	# Callbacks
	
	def on_click(self, key):
	  # on click, popup a dialog with an empty text-entry, and let the user fill it and press ok/Enter.
		self.ask_text()

	def on_middle_click(self):
		"""When middle-clicked the applet get the clipboard 
		   (primary or clipboard buffer content) and translates it """
		content = self.get_from_clipboard()
		if content:
			self.translate(content, self.source.abbrv, self.destination.abbrv)    # content to be translated, the source and destination languages
	
	# Caso eu tivesse usado o widget-type = 'text-entry', o valor do 'content' seria o
	# conteudo string do campo de texto, mas como resultados com strings grandes tem
	# pouca legibilidade na caixa de texto do 'text-entry', deixei o PopupDialog
	# apenas funcionar como ShowDialog (exibindo mensagem). Portanto, o 'content' vem com 
	# o valor igual ao do 'key' e nao como a string contida no campo de texto
	def on_answer_dialog(self, key, content):
		if self.dialog_type == self.DIALOG_QUERY:                                               # dialog popped by the user, he entered the text and pressed Enter/Escape
			if (key == 0 or key == CDApplet.DIALOG_KEY_ENTER) and content:        # ok or Enter
				self.text_to_be_translated = content
				self.translate(content, self.source.abbrv, self.destination.abbrv)  # what to be translated, the source and destination languages
		else:                                                                   # dialog poped by the applet as the result of a translation
			if (key == self.new_translation_key):                                 # new translation on button 0
				self.ask_text()                                                     # popup the empty dialog again
		  # refresh on button 1 (or Enter; Escape is ignored and therefore closes the dialog)
			elif (key == self.edit_translation_key or key == CDApplet.DIALOG_KEY_ENTER):
				self.ask_text(self.text_to_be_translated)                           # popup the dialog again with the latest text entered by the user
	
	def _update_conf_file(self):
		log ("update "+self.cConfFile)
		keyfile = RawConfigParser()
		keyfile.read(self.cConfFile)
		keyfile.set('Configuration', 'source', self.config['slang'])
		keyfile.set('Configuration', 'destination', self.config['dlang'])

	def _switch_destination_language(self, index):
		self.config['dlang'] = index
		self.destination = self.languages[index]
		log ("Switched destination from menu: %s" % self.destination.name)
		self._update_conf_file()
		self.inform_current_destination_language()

	def _switch_source_language(self, index):
		self.config['slang'] = index
		self.source = self.languages[index]
		log ("Switched source from menu: %s" % self.source.name)
		self._update_conf_file()
		self.inform_current_source_language()

	def _switch_languages(self):
		self.config['slang'],self.config['dlang'] = self.config['dlang'],self.config['slang']
		self.source, self.destination = self.destination, self.source
		log ("Switched languages, lets translate from %s to %s" % (self.source.name, self.destination.name))
		self._update_conf_file()
		self.inform_current_destination_language()
	
	def on_menu_select(self, selected_menu):
		if selected_menu == self.clipboard_menu_id:
			content = self.get_from_clipboard()
			if content:
				self.translate(content, self.source.abbrv, self.destination.abbrv)    # what to be translated, the source and destination languages
		elif selected_menu == self.switch_languages_menu_id:
			self._switch_languages()
		elif selected_menu > self.source_language_menu_id:
			self._switch_source_language(selected_menu - self.source_language_menu_id - 1)
		else:
			self._switch_destination_language(selected_menu - self.destination_language_menu_id - 1)

	def on_scroll(self, scroll_up):
		self.source, self.destination = self.destination, self.source
		self.inform_current_destination_language()

	def _build_menu_for_destination_languages(self):
		sub_menu_icon = os.path.abspath("./data/to.png")
		sub_menu_id = self.destination_language_menu_id
		destination_sub_menu = [{'type'  : CDApplet.MENU_SUB_MENU,                # sub-menu
								 'label' : _('To'),
								 'menu'  : CDApplet.MAIN_MENU_ID,                             # belongs to the main menu
								 'id'    : sub_menu_id,
								 'icon'  : sub_menu_icon }]
		index = sub_menu_id + 1
		for language in self.languages:
			destination_sub_menu.append({
				'type'  : CDApplet.MENU_ENTRY,                                        # normal entry
				'label' : language.name,
				'menu'  : sub_menu_id,                                                # belongs to sub-menu "To"
				'id'    : index,
				'icon'  : sub_menu_icon if language is self.destination else '' })
			index += 1
		return destination_sub_menu

	def _build_menu_for_source_languages(self):
		sub_menu_icon = os.path.abspath("./data/from.png")
		sub_menu_id = self.source_language_menu_id
		source_sub_menu = [{'type'  : CDApplet.MENU_SUB_MENU,                     # sub-menu
							'label' : _('From'),
							'menu'  : CDApplet.MAIN_MENU_ID,                                # belongs to the main menu
							'id'    : sub_menu_id,
							'icon'  : sub_menu_icon }]
		index = sub_menu_id + 1
		for language in self.languages:
			source_sub_menu.append({
				'type'  : CDApplet.MENU_ENTRY,                                        # normal entry
				'label' : language.name,
				'menu'  : sub_menu_id,                                                # belongs to sub-menu "From"
				'id'    : index,
				'icon'  : sub_menu_icon if language is self.source else '' })
			index += 1
		return source_sub_menu

	def _build_menu_for_switch_languages(self):
		menu_icon = os.path.abspath("./data/switch.png")
		index = self.switch_languages_menu_id
		tooltip = "%s to %s" % (self.destination.name, self.source.name)
		return [{ 'type'   : CDApplet.MENU_ENTRY,                                 # normal entry
			'label'  : _('Switch languages')+' ('+_('scroll')+')',
			'menu'   : CDApplet.MAIN_MENU_ID,                               # belongs to the main menu
			'id'     : index,
			'icon'   : menu_icon,
			'tooltip': tooltip }]

	def on_build_menu(self):
		items = []
		items += self._build_menu_for_source_languages()
		items += self._build_menu_for_destination_languages()
		items += self._build_menu_for_switch_languages()
		items.append({  'type'  : CDApplet.MENU_SEPARATOR, 'menu':0 })            # belongs to the main menu
		items.append({  'type'  : CDApplet.MENU_ENTRY,                               
					          'label' : _("Translate from clipboard")+' ('+_('middle-click')+')',
					          'menu'  : CDApplet.MAIN_MENU_ID,                          # belongs to the main menu
					          'id'    : self.clipboard_menu_id,
					          'icon'  : 'stock_paste' })
		self.icon.AddMenuItems(items)

	def on_shortkey(self, key):
		if (key == self.config['shortkey_from']):     # user reading on the unknown language, and wants translate to the target language
			self.set_read_mode()
		elif (key == self.config['shortkey_to']):     # user writing in its mother tongue, and wants translate to the target language
			self.set_write_mode()
		self.ask_text()

	def on_drop_data(self, text):
		self.translate(text, self.source.abbrv, self.destination.abbrv)

if __name__ == '__main__':
	Applet().run()
