#
#  opensearch.py : An OpenSearch module for the deskbar applet.
#
#  Copyright (C) 2008 by Arthur Perton
#
#  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.
# 
#  Authors: Arthur Perton <arthurperton at no_spam_me gmail.com>
#  1.0 : Initial release.

from gettext import gettext as _
import deskbar.interfaces.Module
import deskbar.interfaces.Match
from deskbar.handlers.actions.ShowUrlAction import ShowUrlAction
from deskbar.core.Utils import load_icon, url_show
import urllib
import htmllib
from xml.dom import minidom
import xml.parsers.expat
import gtk
import pickle

HANDLERS = ["OpenSearchHandler"]

SETTINGS_FILE = "opensearch.pkl";
#URL_TEMPLATE = "http://blogsearch.google.com/blogsearch_feeds?q={searchTerms}&ui=blg&output=rss"

#WEBSITE_NAME = 0
#WEBSITE_DESCRIPTION_DOCUMENT_URL = 1
#WEBSITE_COMMENT = 2
#WEBSITE_ENABLED = 3
#WEBSITE_URL_TEMPLATE = 4

class OpenSearchAction(ShowUrlAction):
	def __init__(self, title, url):
		ShowUrlAction.__init__(self, title, url)

	def get_verb(self):
		return _("Visit <i>%(name)s</i>")

class OpenSearchMatch(deskbar.interfaces.Match):
	def __init__(self, name=None, url=None, **args):
		deskbar.interfaces.Match.__init__(self, name=name, icon="gtk-network", category="actions", **args)
		self.name = name
		self.add_action(OpenSearchAction(self.name, url))

	def get_category(self):
		return "actions"

	def get_verb(self):
		return _("Website: <b>%(name)s</b>")
                
class OpenSearchHandler(deskbar.interfaces.Module):
	INFOS = {
			"icon": deskbar.core.Utils.load_icon("gtk-find"),
			"name": _("OpenSearch"),
			"description": _("Searches any OpenSearch compliant website"),
			"version": "1.0",
			}
	
	def __init__(self):
		#self.load_websites()
		deskbar.interfaces.Module.__init__(self)
			
	def query(self, query):
		self.load_websites()
		for website in self.websites:
			url_template = self.get_url_template(website[1])
			if url_template != None:
				try:	
					url = url_template.replace("{searchTerms}", urllib.quote_plus(query))
					xml_document = minidom.parse(urllib.urlopen(url))
					root_tag = xml_document.documentElement.tagName
					# atom:
					if root_tag == "feed":
						item_nodes = xml_document.getElementsByTagName("entry")
						for item_node in item_nodes:
							title = item_node.getElementsByTagName("title")[0].firstChild.data
							link = item_node.getElementsByTagName("link")[0].getAttribute("href")
							title = sXmlEntityHelper.unescape(title)
							self._emit_query_ready(query, [OpenSearchMatch(name=title, url=link)])
					# rss:
					elif root_tag == "rss":
						item_nodes = xml_document.getElementsByTagName("item")
						for item_node in item_nodes:
							title = item_node.getElementsByTagName("title")[0].firstChild.data
							link = item_node.getElementsByTagName("link")[0].firstChild.data
							title = XmlEntityHelper.unescape(title)
							self._emit_query_ready(query, [OpenSearchMatch(name=title, url=link)])
				except:
					self._emit_query_ready(query, [OpenSearchMatch(name=_("Search " + website[0] + " for " + query), url=url)])

	def has_config(self):
		return True
    
	def show_config(self, parent):
		dialog = OpenSearchSettingsDialog(parent)
		dialog.run()
		dialog.destroy()
		
	def load_websites(self):
		file = open(SETTINGS_FILE, 'rb')
		self.websites = pickle.load(file)
		file.close()
	
	def get_url_template(self, url):
		#for website in self.websites:
		#try:
		#url = website[1]
		try:
			xml_document = minidom.parse(urllib.urlopen(url))
			url_template = xml_document.getElementsByTagName("Url")[0].getAttribute("template")
			return url_template
		except:
			return None
		#website.append(url_template)
		#except:
		#	pass
			

class OpenSearchSettingsDialog(gtk.Dialog):
	def __init__(self, parent):
		gtk.Dialog.__init__(
				self, _("OpenSearch Settings"), parent,
				gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
				(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
		
		self.set_default_size(480, 400)
		self.connect("destroy", self.on_destroy)
		
		# add a hbox:		
		hbox = gtk.HBox(spacing=12)
		self.vbox.pack_start(hbox)

		# add a vbox containing the treeview on the left:
		#vbox = gtk.VBox(spacing=6)
		#hbox.pack_start(vbox)
		
		# create a liststore for the treeview data:
		self.liststore = gtk.ListStore(str, str, str, 'gboolean')
		self.load_settings(self.liststore)
		#self.liststore.append(['Example', 'http://www.example.com/doc.xml', 'Example Website', True])
		#self.liststore.append(['Other Example', 'http://www.otherexample.com/doc.xml', 'Another Example Website', False])

		# construct the treeview:
		self.treeview = gtk.TreeView(self.liststore)
		#self.treeview.set_border_width(50)

		self.treeview.set_rules_hint(True)
		#hbox.pack_start(self.treeview)

		self.cell0 = gtk.CellRendererToggle()
		self.cell0.set_property("activatable", True)
		self.cell0.connect('toggled', self.on_toggled, self.liststore)

		self.cell1 = gtk.CellRendererText()
		#self.cell2 = gtk.CellRendererText()

		self.treeviewcolumn0 = gtk.TreeViewColumn('Enabled', self.cell0, active=3)
		self.treeviewcolumn1 = gtk.TreeViewColumn('Website', self.cell1)
		self.treeviewcolumn1.set_cell_data_func(self.cell1, self.set_website_text)

		self.treeview.append_column(self.treeviewcolumn0)
		self.treeview.append_column(self.treeviewcolumn1)

		self.treeview.set_tooltip_column(1)

		# add a scrollwindow for the treeview:
		#vscrollbar = gtk.VScrollbar(adjustment=None)
		#hbox.pack_start(vscrollbar)
		#self.treeview.set_vadjustment(vscrollbar.get_adjustment())
		scrollwindow = gtk.ScrolledWindow()
		scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
		scrollwindow.set_shadow_type(gtk.SHADOW_IN)
		scrollwindow.add(self.treeview)
		hbox.pack_start(scrollwindow)

		# add a vbuttonbox containing the buttons on the right:
		vbuttonbox = gtk.VButtonBox()
		vbuttonbox.set_layout(gtk.BUTTONBOX_START)
		hbox.pack_start(vbuttonbox, False, True)
	
		# add the buttons:
		self.add_button = gtk.Button(stock=gtk.STOCK_ADD)
		self.add_button.connect('clicked', self.on_add_button_clicked)
		vbuttonbox.pack_start(self.add_button)

		self.remove_button = gtk.Button(stock=gtk.STOCK_REMOVE)
		self.remove_button.connect('clicked', self.on_remove_button_clicked)
		vbuttonbox.pack_start(self.remove_button)

		self.edit_button = gtk.Button(stock=gtk.STOCK_EDIT)
		self.edit_button.connect('clicked', self.on_edit_button_clicked)
		vbuttonbox.pack_start(self.edit_button)
        
        	# show all:
		self.vbox.show_all()

	def set_website_text(self, column, cell, model, iter):
		cell.set_property('markup', _("<b>" + model.get_value(iter, 0) + "</b>\n" + model.get_value(iter, 2)))
		return

	def on_toggled(self, cell, path, model):
		iter = model.get_iter(path)
		model.set_value(iter, 3, not cell.get_active())
		return
		
	def save_settings(self, model):
		settings = []
		for row in model:
			setting = []
			for value in row:
				setting.append(value)
			settings.append(setting)
		file = open(SETTINGS_FILE, 'wb')
		pickle.dump(settings, file)
		file.close()
		
	def load_settings(self, model=None):
		file = open(SETTINGS_FILE, 'rb')
		settings = pickle.load(file)
		file.close()
		if model == None:
			model = gtk.ListStore(str, str, str, 'gboolean')
		for setting in settings:
			model.append(setting)
	
	def add_website(self, name, url, description):
		self.liststore.append([name, url, description, True])

	def edit_website(self, name, url, description, iter = None):
		if iter == None:
			iter = self.get_selected()
		if iter != None:
			self.liststore.set(iter, 0, name, 1, url, 2, description)

	def remove_website(self, iter):
		self.liststore.remove(iter)	

	def on_add_button_clicked(self, button):
		dialog = OpenSearchAddWebsiteDialog(self)
		dialog.run()
		dialog.destroy()
	
	def on_edit_button_clicked(self, button):
		iter = self.get_selected()
		if iter != None: 
			dialog = OpenSearchEditWebsiteDialog(self)
			dialog.run()
			dialog.destroy()

	def on_remove_button_clicked(self, button):
		iter = self.get_selected()
		if iter != None: 
			self.remove_website(iter)
	
	def on_destroy(self, widget):
		self.save_settings(self.liststore)
		
	def get_selected(self):
		return self.treeview.get_selection().get_selected()[1]
			
class OpenSearchEditWebsiteDialog(gtk.Dialog):
	def __init__(self, parent):
		gtk.Dialog.__init__(
				self, self.get_title(), parent,
				gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR,
				(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, 
				 gtk.STOCK_OK, gtk.RESPONSE_OK))
				 
		self.settings_dialog = parent
		self.connect("response", self.on_response)

		self.set_default_size(400, -1)
		
		hbox = gtk.HBox()
		
		self.vbox.pack_start(child=hbox, padding=10)

		table = gtk.Table(rows=3, columns=2, homogeneous=False)
		table.set_col_spacings(7);
		table.set_row_spacings(7);
		hbox.pack_start(child=table, padding=10)
		
		self.entry0 = gtk.Entry()
		
		label0 = gtk.Label("_Name:")
  		label0.set_use_underline(True)
  		label0.set_mnemonic_widget(self.entry0)
  		label0.set_alignment(0, 0.5)

		table.attach(label0, 0, 1, 0, 1, gtk.FILL, gtk.FILL)
		table.attach(self.entry0, 1, 2, 0, 1, gtk.FILL | gtk.EXPAND, gtk.FILL)
		
		self.entry1 = gtk.Entry()
		
		label1 = gtk.Label("_Location (URL):")
  		label1.set_use_underline(True)
  		label1.set_mnemonic_widget(self.entry1)
		label1.set_alignment(0, 0.5)

		table.attach(label1, 0, 1, 1, 2, gtk.FILL, gtk.FILL)
		table.attach(self.entry1, 1, 2, 1, 2, gtk.FILL | gtk.EXPAND, gtk.FILL)
		
		self.entry2 = gtk.Entry()
		
		label2 = gtk.Label("_Description:")
  		label2.set_use_underline(True)
  		label2.set_mnemonic_widget(self.entry2)
		label2.set_alignment(0, 0.5)
		
		table.attach(label2, 0, 1, 2, 3, gtk.FILL, gtk.FILL)
		table.attach(self.entry2, 1, 2, 2, 3, gtk.FILL | gtk.EXPAND, gtk.FILL)
		
		self.set_entry_texts()
		
		 # show all:
		self.vbox.show_all()
			
	def get_title(self):
		return _("Edit OpenSearch Website")

	def set_entry_texts(self):
		iter = self.settings_dialog.get_selected()
		if iter != None:
			self.entry0.set_text(self.settings_dialog.liststore.get_value(iter, 0))
			self.entry1.set_text(self.settings_dialog.liststore.get_value(iter, 1))
			self.entry2.set_text(self.settings_dialog.liststore.get_value(iter, 2))

	def on_response(self, widget, response_id):
		if response_id == gtk.RESPONSE_OK:
			if not True:
				self.emit_stop_by_name("response")
			self.settings_dialog.edit_website(self.entry0.get_text(), self.entry1.get_text(), self.entry2.get_text())

class OpenSearchAddWebsiteDialog(OpenSearchEditWebsiteDialog):			
	def get_title(self):
		return _("Add OpenSearch Website")
		
	def set_entry_texts(self):
		self.entry0.set_text("")
		self.entry1.set_text("http://")
		self.entry2.set_text("")
		
	def on_response(self, widget, response_id):
		if response_id == gtk.RESPONSE_OK:
			if not True:
				self.emit_stop_by_name("response")
			self.settings_dialog.add_website(self.entry0.get_text(), self.entry1.get_text(), self.entry2.get_text())

		
class XmlEntityHelper():
	@staticmethod		
	def unescape(s):
		want_unicode = False
		if isinstance(s, unicode):
			s = s.encode("utf-8")
			want_unicode = True

		# the rest of this assumes that "s" is UTF-8
		list = []

		# create and initialize a parser object
		p = xml.parsers.expat.ParserCreate("utf-8")
		p.buffer_text = True
		p.returns_unicode = want_unicode
		p.CharacterDataHandler = list.append

		# parse the data wrapped in a dummy element
		# (needed so the "document" is well-formed)
		p.Parse("<e>", 0)
		p.Parse(s, 0)
		p.Parse("</e>", 1)

		# join the extracted strings and return
		es = ""
		if want_unicode:
			es = u""
		return es.join(list)
