#!/usr/bin/env python

# $Id$
import sys
import locale
import gobject
import pygtk
pygtk.require("2.0")
import gtk
import gtk.glade
import gnome
import gnome.ui
from os import path, umask, environ
from threading import Thread
from string import *
import time
from UnixSocketTransport import UnixSocketTransport
import xmlrpclib
import urllib
import socket
from mx import DateTime
from mx.DateTime import DateTimeType
from MetadataService.common import to_xml_params, from_xml_params
from MetadataService import Config
import types

# FIXME: ensure everything is utf-8 encoded when sending down the wire, at all times!
# FIXME:  ensure everything is utf-8 decoded when pulling from the wire
# FIXME: make nice startup error messages in case anything fails
# FIXME: make icons and package



class PulserThread(Thread):
	
	def __init__(self,progressbar):
		Thread.__init__(self)
		self.setDaemon(True)
		self.progressbar = progressbar
		self.stopped = False
		
	def run(self):
		while self.stopped is False:
			gtk.gdk.threads_enter()
			gobject.idle_add(self.progressbar.pulse)
			gtk.gdk.threads_leave()
			time.sleep(0.1)
		
	def stop(self):
		self.stopped = True

def local_to_utc(params):
	"""Take an array, list, dictionary of parameters which contains local DateTimes, and turn them all into UTC DateTimes."""
	f = local_to_utc
	if type(params) is list: return [ f(param) for param in params ]
	elif type(params) is tuple: return tuple([ f(param) for param in params ])
	elif isinstance(params,types.DictType):
		resdict = {}
		for key,value in params.iteritems(): resdict[f(key)] = f(value)
		return resdict
	elif type(params) is DateTimeType:
		# convert the (local) mx.DateTime instance into a (UTC) mx.DateTime instance
		params = params.gmtime()
		return params
	return params

def utc_to_local(params):
	"""Take an array, list, dictionary of parameters which contains UTC DateTimes, and turn them all into local DateTimes."""
	f = utc_to_local
	if type(params) is list: return [ f(param) for param in params ]
	elif type(params) is tuple: return tuple([ f(param) for param in params ])
	elif isinstance(params,types.DictType):
		resdict = {}
		for key,value in params.iteritems(): resdict[f(key)] = f(value)
		return resdict
	elif type(params) is DateTimeType:
		# convert the (local) mx.DateTime instance into a (UTC) mx.DateTime instance
		params = params.localtime()
		return params
	return params

#FIXME this should be a member of the QuickSearch class
config = Config.get()

class Searcher(Thread):
	def __init__(self,query,delegate):
		Thread.__init__(self)
		self.setDaemon(True)
		self.delegate = delegate
		self.query = query
		
	def run(self):
		self.search()
		
	def search(self):
		sockaddr = config.get_socket()
		socket_filename = sockaddr
		# FIXME define once and for all where it will reside
		server_url = "http://localhost/"
# 		import locale
# 		encoding = locale.getpreferredencoding()
		server = xmlrpclib.ServerProxy(server_url,UnixSocketTransport(socket_filename),verbose=0)
# 		print "Query:",self.query
		try:
			query = to_xml_params(local_to_utc(self.query))
			results = server.index.search(query)
			if self.delegate: self.delegate(results)
		except Exception,e:
			if self.delegate:
				self.delegate(sys.exc_info())


	def stop(self):
		self.delegate = None


def get_shared_path():
	testfile = 'version'
	sharedirs = [".",path.join(path.dirname(sys.argv[0]),"../share/gnome-quicksearch")]
	sharepath = None
	for sharedir in sharedirs:
		fname = path.join(path.abspath(sharedir),testfile)
		if path.exists(fname):
			sharepath = path.abspath(sharedir)
			break
	
	if sharepath is None:
		raise Exception, "Quick search shared files " + testfile + " cannot be found in any of " + str(sharedirs) + " default paths"
	
	return sharepath
	
def get_version():
	f = file(path.join(get_shared_path(),"version"))
	vers = f.readlines()
	f.close()
	return vers[0].strip()

sys.path[0] = get_shared_path()



debug = True


def debug_print(something):
	if debug:
		print something
		sys.stdout.flush()

class DefaultingEntry(gtk.Entry):
	def __init__(self,max=0):
		gtk.Entry.__init__(self,max)
		self.set_activates_default(True)
		
		
class SearchExpressionWidget:
	attribute= None
	label = None
	
	def __init__(self,attribute,attribute_name):
		assert type(attribute) is str and len(attribute) > 0
		assert type(attribute_name) is str and len(attribute_name) > 0
# 		assert type(label) is str and len(label) > 0
# 		self.label = label
		self.attribute = attribute
		self.attribute_name = attribute_name
		
	def get_expression(self):
		raise NotImplementedError

	def restore_separatable(self):
		self.separatable.hide()
		self.separatable.reparent(self)
		self.separatable.show()
	

class DiscloserExpressionWidget(gtk.VBox):
	def __init__(self,label,expression):
# 		gtk.Expander.__init__(self)
		gtk.VBox.__init__(self)
		
		self.hbox = gtk.HBox(spacing=8)
		
		self.label = label
		self.expander = gtk.Expander()
		self.expander.set_use_underline(True)
		self.expander.set_label(self.label + ":")
		
		a = gtk.Alignment(xalign=0.0,yalign=0.5,xscale=1.0,yscale=1.0)
		a.set_padding(4,0,0,0)
# 		a = gtk.HBox()
		
		a.add(self.expander)
		
		self.hbox.pack_start(a)
# 		self.hbox.pack_start(gtk.Label("CRAP"))
		
		self.pack_start(self.hbox)
		
		self.expression = expression
		self.expression.hide()
		self.expression.set_no_show_all(True)
		
		self.pack_start(self.expression)
		
		
# 		self.label_widget = gtk.HBox(spacing=8)
# 		
# 		
# 		self.label_l = gtk.Label()
# 		self.label_l.set_use_underline(True)
# 		self.label_l.set_label(self.label + ":")
# 		
# 		self.label_widget.pack_start(self.label_l)
# 		self.expander.set_label_widget(self.label_widget)
		
		
		self.expander.connect_object_after("activate",self.on_disclose,None)
		
# 		self.add(self.expression)
		self.show_all()
	
	def get_expanded(self): return self.expander.get_expanded()
	def get_expression(self):
		if not self.expander.get_expanded(): return None
		return self.expression.get_expression()

	def emit(self,s):
		if s == "conditional-activate":
			if self.expander.get_expanded() is False:
				self.expander.emit("activate")
			else:
				self.expression.grab_focus()
		elif s == "activate": self.expander.emit(s)
		else: gtk.VBox.emit(self,s)
		
	def on_disclose(self,stuff=None):
		if hasattr(self.expression,"separatable") and hasattr(self.expression,"restore_separatable"):
			if self.expander.get_expanded() is True:
# 				self.label_l.set_label(self.label)
				self.expression.separatable.reparent(self.hbox)
#				pass
# 				self.expression.separatable.set_size_request(200,-1)
# 				print self.size_request()[0]
# 				self.label_widget.set_size_request(300,-1)
# 				self.label_widget.pack_start(self.expression.separatable)
			else:
				self.expression.restore_separatable()
# 				self.label_l.set_label(self.label + ":")
		
		if self.expander.get_expanded() is True:
			self.expression.show()
			self.expression.grab_focus()
		else:
			self.expression.hide()
			self.expander.grab_focus()

class TextSearchExpressionWidget(SearchExpressionWidget,gtk.VBox):
	checkbutton = None
	searchtype = None
	entry = None
	map_text_to_operator = { "contains words":"=?","matches pattern":"=*","contains string":"contains","is exactly":"=","matches expression":"=~" }
	operator_sort_order = [ "contains string","contains words","is exactly","matches pattern" ]
	
	def __init__(self,attribute,attribute_name,short=True):
		SearchExpressionWidget.__init__(self,attribute,attribute_name)
		gtk.VBox.__init__(self,spacing=4)
		
		if not short:
			self.operator_sort_order = [ "contains words" ]
		
		self.searchtype = gtk.combo_box_new_text()
		self.searchtype.connect_object("changed",self.searchtype_changed,None)
# 		self.searchtype.set_size_request(40,-1)

		self.casecheckbox = gtk.CheckButton("Match case")
		self.casecheckbox.set_active(True)
		self.separatable = self.searchtype
		
		for a in self.operator_sort_order: self.searchtype.append_text(a)
		self.searchtype.set_active(0)
		
		self.hbox = gtk.HBox(spacing=8)
		self.entry = DefaultingEntry(max=128)
		self.entry.set_size_request(80,-1)
		
		self.pack_start(self.searchtype)
		self.hbox.pack_start(self.entry)
		self.hbox.pack_start(self.casecheckbox,fill=False,expand=False)
		self.pack_start(self.hbox)
		self.show_all()
	
	def searchtype_changed(self,w=None,e=None):
		s = self.searchtype
		operator = self.map_text_to_operator[s.get_model().get_value(s.get_active_iter(),0)]
		if operator == "=?":
			self.casecheckbox.set_active(False)
			self.casecheckbox.set_sensitive(False)
		else:
			self.casecheckbox.set_sensitive(True)
# 		self.set_search_type(searchtype)
		
	
	def get_expression(self):
		attribute = self.attribute
		s = self.searchtype
		operator = self.map_text_to_operator[s.get_model().get_value(s.get_active_iter(),0)]
		value = self.entry.get_text().strip()
		if len(value) < 1 and "=?" not in operator:
			raise ValueError,"The %s field needs at least one letter"%self.attribute_name
		if "=*" in operator and "*" not in value and "?" not in value:
			raise ValueError,"The %s field needs to contain at least one wildcard (*?)"%self.attribute_name
		if "=?" in operator:
			words = [e for e in value.split(" ") if e ]
			errmsg = "The %s field needs to contain at least one word, and each word must have a minimum of 4 letters and no punctuation or signs"%self.attribute_name
			if not words: raise ValueError,errmsg
			for w in words:
				if len(w) < 4: raise ValueError,errmsg
				
		
		if self.casecheckbox.get_active() is False and operator != "=?":
			operator = "i" + operator
		return { "operator" : operator, "attribute" : attribute, "value" : value }

	def grab_focus(self):
		self.entry.grab_focus()

class DateTimeSearchExpressionWidget(SearchExpressionWidget,gtk.VBox):
	checkbutton = None
	searchtype = None
	entry = None
	map_text_to_operator = { "before":"<","after":">","on or before":"<=","on or after":">="}
	operator_sort_order = [ "on or after","on or before","after","before" ]
	
	def __init__(self,attribute,attribute_name):
		SearchExpressionWidget.__init__(self,attribute,attribute_name)
		gtk.VBox.__init__(self,spacing=4)
		
		self.searchtype = gtk.combo_box_new_text()
		self.datepicker = gtk.Calendar()
		
		self.separatable = self.searchtype
		
		for a in self.operator_sort_order: self.searchtype.append_text(a)
		self.searchtype.set_active(0)
		
		hbox = gtk.HBox(spacing=4)
		hbox.pack_start(self.searchtype)
		
		self.hour = gtk.SpinButton(adjustment=gtk.Adjustment(upper=23,step_incr=1))
		self.minute = gtk.SpinButton(adjustment=gtk.Adjustment(upper=59,step_incr=1))
		self.second = gtk.SpinButton(adjustment=gtk.Adjustment(upper=59,step_incr=1))

		now = DateTime.localtime()
		self.hour.set_value(now.hour)
		self.minute.set_value(now.minute)
		self.second.set_value(now.second)
		
		hbox.pack_start(gtk.Label("At:"))
		
		for a in [self.hour,self.minute,self.second]:
			a.set_snap_to_ticks(True)
			a.set_size_request(40,-1)
			hbox.pack_start(a)
		
		hbox.pack_start(gtk.Label("of day:"))
		
			
# 		label = gtk.Label("of:")
# 		hbox.pack_start(label)
		
		self.pack_start(hbox)
		self.pack_start(self.datepicker)
		self.show_all()
	
	def get_expression(self):
		attribute = self.attribute
		s = self.searchtype
		operator = self.map_text_to_operator[s.get_model().get_value(s.get_active_iter(),0)]
		date = self.datepicker.get_date()
		time = (self.hour.get_value_as_int(),self.minute.get_value_as_int(),self.second.get_value_as_int())
		value = DateTime.DateTime(date[0],date[1]+1,date[2],time[0],time[1],time[2])
		return { "operator" : operator, "attribute" : attribute, "value" : value }

	def grab_focus(self):
		self.hour.grab_focus()

class BaseDirExpressionWidget(SearchExpressionWidget,gtk.HBox):
	searchtype = None
	entry = None
	checkbutton = None
	select_dir = None
	operator = "/="
	
	def __init__(self,attribute,attribute_name):
		SearchExpressionWidget.__init__(self,attribute,attribute_name)
		gtk.HBox.__init__(self,spacing=8)
		
		self.select_dir = gtk.Button(label="_Browse...")
		self.select_dir.connect_object("clicked",self.select_dir_clicked,None)
		
		self.separatable = self.select_dir
		
		self.entry = DefaultingEntry(max=2048)
		self.entry.set_text(environ["HOME"])
		
		self.pack_start(self.entry)
		self.pack_start(self.select_dir)
		self.show_all()

	def select_dir_clicked(self,widget,data=None):
		buttons = ( gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OK,gtk.RESPONSE_OK)
		fs = gtk.FileChooserDialog(title="Choose a folder to search in",action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,buttons=buttons)
# 		fs.connect("response",self.select_dir_responded)
		fs.set_select_multiple(False)
		fs.set_filename(self.entry.get_text())
		response = fs.run()
		if response == gtk.RESPONSE_OK:
			filename = fs.get_filename()
			self.entry.set_text(filename)
		else: pass
		fs.destroy()
	
	def get_expression(self):
		attribute = self.attribute
		s = self.searchtype
		operator = self.operator
		value = self.entry.get_text().strip()
		if len(value) < 1:
			raise ValueError,"Please select or type a folder"
		if not path.isdir(value):
			raise ValueError,"Please select a valid, existing folder"
		return { "operator" : operator, "attribute" : attribute, "value" : value }

	def grab_focus(self):
		self.entry.grab_focus()

class SearchForm:
	"""Abstract interface for any search form"""

	container = None
	
	def get_expression(self):
		expressions = []
		for exp in self.expressions:
			expression = exp.get_expression()
			if expression is not None:
				expressions.append(expression)
		if len(expressions) > 0:
			return { "aggregation": "and","expressions":expressions }
		
	def __init__(self): pass
	
class MusicSearchForm(SearchForm,gtk.VBox):
	def __init__(self):
		SearchForm.__init__(self)
		gtk.VBox.__init__(self,spacing=8)
		
		self.expressions = []
		for a in [("artist","Artist","_Artist"),("album","Album","Albu_m"),("title","Title","_Title")]:
			expression = TextSearchExpressionWidget(a[0],a[1])
			discloser = DiscloserExpressionWidget(a[2],expression)
			self.expressions.append(discloser)
		for exp in self.expressions: self.pack_start(exp)
			
		self.show_all()

 	def grab_focus(self):
		self.expressions[0].emit("conditional-activate")
# 
# 		e = self.expressions[0]
# 		e.grab_focus()

	def get_expression(self):
		expressions = []
		expression = SearchForm.get_expression(self)
		if expression: expressions.append(expression)
		mp3exp = { "operator" : "=*", "attribute" : "mimetype", "value" : "audio/*" }
		oggexp = { "operator" : "=", "attribute" : "mimetype", "value" : "application/ogg" }
		audioexp = { "aggregation": "or","expressions":[mp3exp,oggexp] }
		expressions.append(audioexp)
		return { "aggregation": "and","expressions":expressions }

class FileSearchForm(SearchForm,gtk.Expander):
	def __init__(self):
		SearchForm.__init__(self)
		gtk.Expander.__init__(self)
		
		self.set_use_underline(True)
		self.set_label("File search options" + ":")
		
		self.vbox = gtk.Alignment(xscale=1.0,yscale=1.0)
		self.vbox.set_padding(8,8,8,0)
# 		gtk.VBox.__init__(self,spacing=8)
		
# 		self.label = gtk.Label()
# 		self.label.set_markup("<b>File search options</b>")
		
		self.container = gtk.VBox(spacing=8)
		
# 		self.pack_start(self.label)
		
		self.expressions = [
			DiscloserExpressionWidget(
			"Look in _folder",BaseDirExpressionWidget("filename","Folder")),
			DiscloserExpressionWidget(
			"Last mo_dified",DateTimeSearchExpressionWidget("modification_date","Modification date"))
			]
		
		for exp in self.expressions: self.container.pack_start(exp)
		
		self.vbox.add(self.container)
		self.add(self.vbox)
		
		
# 		self.add(self.container)
		
		self.show_all()

 	def grab_focus(self):
		self.set_expanded(True)
		self.expressions[0].emit("conditional-activate")
# 		self.expressions[0].grab_focus()

	def get_expression(self):
		if self.get_expanded():
			return SearchForm.get_expression(self)

class DocumentsSearchForm(SearchForm,gtk.VBox):
	def __init__(self):
		SearchForm.__init__(self)
		gtk.VBox.__init__(self,spacing=8)
		
		
		
		self.expressions = []
		for a in [("contents","Text","_Text"),("title","Title","Titl_e")]:
			expression = TextSearchExpressionWidget(a[0],a[1],short=False)
			discloser = DiscloserExpressionWidget(a[2],expression)
			self.expressions.append(discloser)
		for exp in self.expressions: self.pack_start(exp)
		
# 		self.expressions = []
# 		tsew = ContentsSearchExpressionWidget()
# 		self.expressions.append(tsew)
# 		for exp in self.expressions: self.pack_start(exp)
			
		self.show_all()

 
 	def grab_focus(self):
		self.expressions[0].emit("conditional-activate")
# 		e.set_expanded(True)
# 		e.grab_focus()
		
 		



class GnomeQuicksearch (gtk.glade.XML):
	config = None
	windows = 0
	
	def resize_search_scroller (self,w=None,e=None,f=None):
		viewport = self.get_widget("search_utils_viewport")
		scroller = self.get_widget("search_utils_scroller")
		viewport.set_size_request(e.width+32,-1)
# 		scroller.set_size_request(e.width,-1)

	def __init__ (self):
		self.increment_window_count()
		gtk.glade.XML.__init__(self,path.join(get_shared_path(),'gnome-quicksearch.glade'))
		self.search_window = self.get_widget("search_window")
		self.load_config()
		self.connect_signals()
		self.prepare_widgets()

	def search_window_closed(self,caller=None,data=None):
		self.close_window()

	def connect_signals(self):
		self.signal_autoconnect(self)

# 	def on_case_sensitive_searching_activate(self,w,data=None):
# 		if w.get_active(): self.config["case-sensitive"] = True
# 		else: self.config["case-sensitive"] = False

	def search_type_changed(self,widget=None):
		s = self.search_type
		searchtype = self.map_search_type_to_constructor[s.get_model().get_value(s.get_active_iter(),0)]
		self.set_search_type(searchtype)

	def drag_data_get(self,widget,drag_context,selection_data,info,timestamp):
		
		urls = []
		def foreach_func(treemodel,path,iterator):
			url = treemodel.get_value(iterator,0)
			urls.append(url)
		
		selection = self.results_view.get_selection()
		selection.selected_foreach(foreach_func)

		selection_data.set('text/uri-list',8,"\n".join(urls))
		
	def drag_data_delete(self,widget,drag_context,param1=None):
		# FIXME does not seem to be working when moving files from konqueror
		iters = []
		# collect all selected iterators
		def foreach_func(treemodel,path,iterator):
			iters.append(iterator)
		
		selection = self.results_view.get_selection()
		selection.selected_foreach(foreach_func)
		
		# remove all data rows corresponding to the iterators
		for iterator in iters: self.results.remove(iterator)
				
	search_type_order = [
		"Text documents","Music", "All files"
	]
	map_search_type_to_constructor = {
		"Text documents":DocumentsSearchForm,
		"All files":None,
		"Music":MusicSearchForm,
	}
	
	def prepare_widgets(self):
		a = "gnome-quicksearch.png"
		b = get_shared_path()
		c = path.join(b,a)
 		self.search_window.set_icon_from_file(c)
 		self.get_widget("about_image").set_from_file(c)
 		self.get_widget("info_dialog").set_icon_from_file(c)
 		self.get_widget("warning_dialog").set_icon_from_file(c)
 		self.get_widget("error_dialog").set_icon_from_file(c)
 		self.get_widget("about_dialog").set_icon_from_file(c)
		
		title = "<span weight=\"bold\"><big><big>Quick search</big></big></span>"
		version = "<b>version "+get_version()+"</b>"
		revinfo = "$URL: http://localhost/repos/gnome-quicksearch/trunk/gnome-quicksearch $"
		subtitle = "A fast and powerful search tool. Part of the Search services project."
		
		self.get_widget("label_about").set_markup(title + "\n" + version + "\n" + subtitle)
		self.get_widget("label_revinfo").set_text(revinfo)
		
		self.results = gtk.ListStore(str,float,str,str)
		self.results_view = self.get_widget("results_view")
		self.results_view.set_model(self.results)
# 		e = 
# 		def func(column, cell_renderer, treemodel, iterator, user_data=None):
# 			score = treemodel.get_value(iterator,1)
# 			cell_renderer.set_property("background", "gray")
# 			cell_renderer.set_property("background-set", True)
# 			pass
		
# 		c.set_cell_data_func(e, func, data=None)
		c = gtk.TreeViewColumn("Score",gtk.CellRendererText(),text=1)
		c.set_sort_indicator(True)
		c.set_sort_column_id(1)
		c.set_resizable(False)
		self.results_view.insert_column(c,-1)
		c = gtk.TreeViewColumn("File name",gtk.CellRendererText(),text=2)
		c.set_sort_indicator(True)
		c.set_sort_column_id(2)
		c.set_resizable(True)
		self.results_view.insert_column(c,-1)
		c = gtk.TreeViewColumn("Located in",gtk.CellRendererText(),text=3)
		c.set_sort_indicator(True)
		c.set_sort_column_id(3)
		c.set_resizable(True)
		self.results_view.insert_column(c,-1)
		
		targets = [('text/uri-list', 0, 0)]
		self.results_view.connect_object("drag-data-get",self.drag_data_get,None)
		self.results_view.connect_object("drag-data-delete",self.drag_data_delete,None)
		selection = self.results_view.get_selection()
		selection.set_mode(gtk.SELECTION_MULTIPLE)
		self.results_view.enable_model_drag_source(gtk.gdk.LEFTBUTTON, targets, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | gtk.gdk.ACTION_ASK)
		
		self.cache_search_widgets = []

		self.search_type = self.get_widget("search_type")
		for a in self.search_type_order: self.search_type.append_text(a)
		self.search_type.set_active(0)
		
		self.file_search_form_widget = FileSearchForm()
		container = self.get_widget("file_search_form_container")
		container.pack_start(self.file_search_form_widget)

# 		self.get_widget("case_sensitive_searching").set_active(self.config["case-sensitive"])
		
		# We finally show the window
# 		self.search_window.resize(self.config["width"],self.config["height"])
		if self.config["height"] and self.config["width"]:
			self.search_window.resize(self.config["width"],self.config["height"])
		if self.config["x"] and self.config["y"]:
			self.search_window.move(self.config["x"],self.config["y"])
		self.search_window.show()
	
	def set_search_type(self,constructor):
		widget = None
		for w in self.cache_search_widgets:
			if w.__class__ == constructor:
				widget = w
		if widget is None:
			if constructor is None: widget = constructor
			else: widget = constructor()
			self.cache_search_widgets.append(widget)
		
		container = self.get_widget("search_form_widget_container")
		container.foreach(container.remove) # clear the current list
		self.search_form_widget = widget
		if self.search_form_widget is not None:
			container.pack_start(self.search_form_widget)
			container.show()
		else:
			container.hide()
 		self.focus_search_form_widget()
		
	def focus_search_form_widget(self):
		if self.search_form_widget is not None:
			self.search_form_widget.grab_focus()
		else:
			self.file_search_form_widget.grab_focus()
		

		
	def set_status_text(self,text):
		widget = self.get_widget("appbar")
		widget.push("%s"%text)
	
	def quit(self,caller=None,extraobject=None):
		self.save_config()
		gtk.main_quit()
		
	def close_window(self,caller=None,extraobject=None):
		self.decrement_window_count()
		self.search_window.destroy()
	
	def increment_window_count(self):
		GnomeQuicksearch.windows = GnomeQuicksearch.windows + 1
		
	def decrement_window_count(self):
		GnomeQuicksearch.windows = GnomeQuicksearch.windows - 1
		if GnomeQuicksearch.windows == 0: self.quit()
		
	def show_dialog(self,dialogtype,message,title=None):
		if title:
			dialogtitle = title
			title = "<span weight='bold' size='larger'>" + title + "</span>\n\n"
		else:
			dialogtitle = "Quick search - " + dialogtype
			title = ""
		self.get_widget(dialogtype + "_message").set_markup(title+message)
		self.get_widget(dialogtype + "_dialog").set_title(dialogtitle)
		self.get_widget(dialogtype + "_dialog").set_transient_for(self.get_widget("search_window"))
		self.get_widget(dialogtype + "_dialog").present()

	def show_error_dialog(self,message,title=None):
		return self.show_dialog("error",message,title)
	
	def show_info_dialog(self,message,title=None):
		return self.show_dialog("info",message,title)
	
	def show_warning_dialog(self,message,title=None):
		return self.show_dialog("warning",message,title)
	
	def show_about_dialog(self,caller=None):
		self.get_widget("about_dialog").present()
		
	def dismiss_error_dialog(self,caller=None,e=None):
		self.get_widget("error_dialog").hide()
		return True
		
	def dismiss_about_dialog(self,caller=None,e=None):
		self.get_widget("about_dialog").hide()
		return True
		
	def dismiss_info_dialog(self,caller=None,e=None):
		self.get_widget("info_dialog").hide()
		return True
		
	def dismiss_warning_dialog(self,caller=None,e=None):
		self.get_widget("warning_dialog").hide()
		return True

# 	def case_insensitivize(self,exp):
# 		if exp.has_key("aggregation"):
# 			exp2 = {}
# 			exp2["aggregation"] = exp["aggregation"]
# 			exp2["expressions"] = [ self.case_insensitivize(a) for a in exp["expressions"] ]
# 			exp = exp2
# 		elif type(exp["value"]) is str:
# 			if self.config["case-sensitive"] is False:
# 				if "/" not in exp["operator"] and exp["operator"] != "=?" :
# 					exp["operator"] = "i" + exp["operator"]
# 
# 		return exp

		
	def search_button_clicked(self,caller=None,e=None):
		expression1 = None
		if self.search_form_widget:
			try: expression1 = self.search_form_widget.get_expression()
			except Exception,e:
				self.show_info_dialog(str(e)+".","Please verify the search terms")
				return
		try: expression2 = self.file_search_form_widget.get_expression()
		except Exception,e:
			self.show_info_dialog(str(e)+".","Please verify the search terms")
			return
		
		expressions = []
		if expression1: expressions.append(expression1)
		if expression2: expressions.append(expression2)
		expression = { "aggregation":"and","expressions":expressions }
		
		if len(expression["expressions"]):
#			print "Before:",expression
# 			expression = self.case_insensitivize(expression)
#			print "After:",expression
			query = { "expression" : expression }
			self.search_started()
			self.current_searcher = Searcher(query,self.results_delegate)
			self.current_searcher.start()
		else:
# 			for a in range(7):
# 				self.get_widget("appbar").get_progress().pulse()
# 				while gtk.events_pending(): gtk.main_iteration(block=False)
# 				time.sleep(0.010)
			self.show_info_dialog("You haven't selected any search options.  Please select at least one and try again.","Please verify the search terms")
# 			self.get_widget("appbar").get_progress().set_fraction(0.0)

	
	def enable_stop_button(self):
		self.get_widget("stop_button").set_sensitive(True)
		self.get_widget("stop_button").grab_default()
# 		self.get_widget("stop_button").set_default(True)
		
	def disable_stop_button(self):
		self.get_widget("stop_button").set_sensitive(False)
		
	def enable_search_buttons(self):
		self.get_widget("search_button").set_sensitive(True)
		self.get_widget("search_button").grab_default()
		
	def disable_search_buttons(self):
		self.get_widget("search_button").set_sensitive(False)

	def focus_search_buttons(self):
		self.get_widget("search_button").grab_focus()
	
	def stop_button_clicked(self,caller=None,e=None):
		if hasattr(self,"current_searcher"):
			self.current_searcher.stop()
			self.set_status_text("Search stopped")
		self.search_stopped()
			
	def reset_search(self,caller=None,e=None):
		self.stop_button_clicked()
		self.results.clear()
		self.search_type.set_active(0)
		self.focus_search_form_widget()
		self.set_status_text("Search cleared")
		
	def new_search(self,caller=None,e=None):
		n = GnomeQuicksearch()
		
	def key_press_event(self,caller=None,e=None):
		if e.keyval == 65307: self.stop_button_clicked()
	
	def results_delegate(self,results):
		gtk.gdk.threads_enter()
		gobject.idle_add(self.search_ended)
		gobject.idle_add(self.process_results,results)
		gtk.gdk.threads_leave()
		
	def search_started(self):
		self.disable_search_buttons()
		self.enable_stop_button()
		self.set_status_text("Querying the local metadata service...")
		progressbar = self.get_widget("appbar").get_progress()
		self.pulser_thread = PulserThread(progressbar)
		self.pulser_thread.start()
	
	def search_ended(self):
		if hasattr(self,"pulser_thread"): self.pulser_thread.stop()
		self.disable_stop_button()
		self.focus_search_results()
		self.enable_search_buttons()
		self.get_widget("appbar").get_progress().set_fraction(1.0)
		
	def search_stopped(self):
		self.disable_stop_button()
		self.enable_search_buttons()
		self.get_widget("appbar").get_progress().set_fraction(0.0)
		if hasattr(self,"pulser_thread"): self.pulser_thread.stop()

	def focus_search_results(self):
		self.results_view.grab_focus()

	def process_results(self,results):
		if type(results) is list:
			results = utc_to_local(from_xml_params(results))
			self.results.clear()
			self.set_status_text("Listing %s results..."%len(results))
			self.results_view.set_model(gtk.ListStore(str,float,str,str))
			counter = 0
			for a in results:
				url = a["uri"]
				if a.has_key("score"): relevance = a["score"]
				else: relevance = 1.0
# 				a = urllib.url2pathname(a)
				if url.startswith("file://"):
					dd = url[7:]
					if dd == "/":
						filename = "/"
						directory = "/"
					else:
						filename = path.basename(dd)
						directory = path.dirname(dd)
					self.results.append((url,relevance,filename,directory))
					counter += 1
			if counter == 1: f = "%s result"
			else: f = "%s results"
			self.set_status_text(f % counter)
			self.results_view.set_model(self.results)
		elif type(results) is tuple:
			self.focus_search_buttons()
			exception_class = results[0]
			exception_instance = results[1]
			exception_traceback = results[2]
			if exception_class is socket.error and exception_instance[0] in (111,2):
				msg = "The local metadata service cannot be contacted"
				self.show_error_dialog("The metadata service cannot be contacted.  Quick search uses the metadata service to quickly search for information.  Please ensure the metadata service is running properly.","Cannot contact the metadata service")

			elif exception_class is xmlrpclib.Fault:
				msg = "The metadata service encountered an error"
				self.show_error_dialog("The metadata service encountered an error while performing your search.  At this stage of development, some search types produce errors.  Please check the metadata service log for errors and/or change your search and retry.","The metadata service encountered an error")
			
			else:
				import traceback
				tb = traceback.format_tb(exception_traceback)
				error_message = str(exception_instance)+"\n"+"".join(tb)
				error_message = error_message.replace("<","[").replace(">","]")
				msg = "Unknown error during search (%s)"%str(exception_instance)
				self.show_error_dialog("An unknown error took place.  Please submit the error message to the Quick search developers.\n\nError message: %s"%error_message,"Unknown error during search")
			self.set_status_text(msg)
		
	def run_uri(self,uri): gnome.url_show(uri)
	
	def results_view_row_activated(self,view=None,b=None,c=None):
		
		def foreach_func(treemodel,path,iterator):
			filename = treemodel.get_value(iterator,0)
			self.run_uri(filename)
		
		selection = view.get_selection()
		selection.selected_foreach(foreach_func)

	def load_config(self):
 		self.config = dict()
		self.config["case-sensitive"] = True
		self.config["height"] = None # defaults
		self.config["width"] = None #defaults
		self.config["x"] = None
		self.config["y"] = None
		
# 		self.config["app_visible"] = True
# 		self.config["joystick_device"] = None
# 		self.config["app_priorities"] = ""
# 		
 		try:
 			fn = path.expanduser("~") + "/.gnome-quicksearch.conf"
 			f = open(fn,"r")
 			lines = f.readlines()
 			
 			for line in lines:
 				key, value = split(line,"=")
 				key = strip(key)
 				value = strip(value)
 				if key == "case-sensitive":
 					if value == "true": self.config["case-sensitive"] = True
 					else: self.config["case-sensitive"] = False
 				if key == "width": self.config["width"] = int(value)
 				if key == "height": self.config["height"] = int(value)
 				if key == "x": self.config["x"] = int(value)
 				if key == "y": self.config["y"] = int(value)
# 				if key == "joystick_device":
# 					self.config["joystick_device"] = value
# 				if key == "app_priorities":
# 					self.config["app_priorities"] = value
 			f.close()
# 
 		except IOError, e:
			pass
			
	

	def save_config(self):
# 		debug_print( "Saving config")
 		fn = path.expanduser("~") + "/.gnome-quicksearch.conf"
 		umask(077)
 		f = open(fn,"w")
 		
# 		string = "app_priorities = " + self.__get_app_priorities() + "\n"
# 		f.write(string)
# 		
 		if self.config["case-sensitive"]:
 			string = "case-sensitive = true" + "\n"
 		else:
 			string = "case-sensitive = false" + "\n"
			
		string += "width = %s" % self.search_window.get_size()[0] + "\n"
 		string += "height = %s" % self.search_window.get_size()[1] + "\n"
		string += "x = %s" % self.search_window.get_position()[0] + "\n"
 		string += "y = %s" % self.search_window.get_position()[1] + "\n"
		
 		f.write(string)
# 		
# 		if self.device:
# 			string = "joystick_device = " + self.device.device.name + "\n"
# 			f.write(string)
# 		
 		f.close()
		pass

	def open_website(self,caller = None):
		self.run_uri("http://www.amautacorp.com/staff/Rudd-O/projects/search-services/")
		
	def open_author_website(self,caller = None):
		self.run_uri("http://www.amautacorp.com/staff/Rudd-O/")
		
	def send_mail_to_author(self,caller = None):
		self.run_uri("mailto:rudd-o@amautacorp.com")
	
	def open_gpl(self,caller = None):
		self.run_uri("http://www.fsf.org/copyleft/gpl.txt")


def main():

	program = gnome.program_init("gnome-quicksearch",get_version())
	program.parse_args()

	app = GnomeQuicksearch()

	gtk.threads_init()
	
	gtk.main()


if __name__ == "__main__":
	main()


