#!/usr/bin/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 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 Library 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.

# sab ott 27 21:19:16 EDT 2001 Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
#	My first python program
# mar ott 30 16:18:46 EST 2001 Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
#	Ok, now it works (mostly :-) Still lacking keybindings, but don't forget I'm a newbie :-P
#ven nov 02 18:22:57 EST 2001 Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
#	Some work done with popups and menus. Pipes still don't work reliably. Boh...
#mar nov 06 20:28:13 EST 2001 Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
#	Some cleanup and made a class out of basic global interface.
#dom dic 23 18:37:24 CET 2001 Giuseppe "Cowo" Corbelli <cowo@lugbs.linux.it>
#	Bug fixed when global launched while in directory without GTAGS
#	Tag search/complete base text search order:
#		1: Selected text
#		2: Meaningful text before cursor
#		3: Text in global entry
import os
import string
import re
import glimmer
from _gtk import *
from gtk import *
from GTK import *
from GDK import *
from gnome.ui import *
import gtk
import GTK
import _gnome
import _gnomeui
import errno

#####################################################################
# Basic no-gui global interface. Pipes and system
#####################################################################
class Tglobal_interface:
	__global_path = None
	__project_path = None
	__completionlist = []
	
	def __init__(self, project_path):
		"""
		Args: directory containing global indexing files
		"""
		for dir in string.split (os.environ['PATH'], ':'):
			self.__global_path = os.path.join(dir,"global")
			if os.access(self.__global_path, os.X_OK|os.R_OK):
				break
			else:
				self.__global_path = None
		if not self.__global_path:
			raise "GlobalNotFound", "global executable not available in path"
		if os.path.isdir(project_path) and os.path.exists(project_path):
			self.__project_path = project_path
		else:
			raise "GlobalNoSuchDirectory", "Specified project path ("+project_path+") is invalid"
		if not os.access(os.path.join(self.__project_path, "GTAGS"), os.R_OK):
			self.__project_path = None
			raise "GlobalNoGTAGSFile", "No readable GTAGS file found at path "+project_path

	def project_path (self):
		return self.__project_path

	def getattrib (self, attribname):
		try:
			value = self.locals()[attribname]
		except:
			return None
		else:
			return value

	def setattrib (self, attribname, value):
		try:
			locals()[attribname]
		except:
			return None
		else:
			self.locals()[attribname] = value
			return value

	def update_gtags (self, *args):
		retcode = os.system (self.__global_path+" -u")>>8
		if not retcode == 0:
			raise "GlobalExecutionError", os.strerror(retcode)

	def completions (self, initial_string):
		"Starts global -c 'hint' and parses output. Returns a list of [function name]"
		self.__completionlist=[]
		os.chdir (self.__project_path)
		again = 5
		while again:
			pipe = os.popen(self.__global_path+" -c '"+initial_string+"'", 'r')
			possible = 'x'
			while not possible=="":
				possible = string.strip (pipe.readline())
				if not possible=="":
					self.__completionlist = self.__completionlist + [possible]
			code = pipe.close()
			if not code==None:
				raise "GlobalExecutionError", os.strerror(code)
				return []
			if len(self.__completionlist) == 0:
				again = again - 1
			else:
				break
		return self.__completionlist
	
	def tags (self, initial_string):
		"Starts global -x '^hint.*' and parses output. Returns a list of tuples (function call, line, file, details)"
		self.__completionlist=[]
		os.chdir (self.__project_path)
		again = 5
		while again:
			pipe = os.popen(self.__global_path+" -ax '^"+initial_string+".*'", 'r')
			possible = 'x'
			while not possible=="":
				possible = string.strip (pipe.readline())
				if not possible=="":
					reobj = re.search ("^(\w+)\s+(\d+)\s+([\w\/\-\.]+)\s+(.+)$", possible)
					self.__completionlist.append ( (reobj.group(1), reobj.group(2), reobj.group(3), reobj.group(4)) )
			code = pipe.close()
			if not code==None:
				raise "GlobalExecutionError", os.strerror(code)
				return []
			if len(self.__completionlist) == 0:
				again = again - 1
			else:
				break
		return self.__completionlist
		
	def symbols (self, initial_string):
		"Starts global -gx 'hint' and parses output. Returns a list of tuples (symbol, line, file, details)"
		self.__completionlist=[]
		os.chdir (self.__project_path)
		again = 5
		while again:
			pipe = os.popen(self.__global_path+" -gx '"+initial_string+"'", 'r')
			possible = 'x'
			while not possible=="":
				possible = string.strip (pipe.readline())
				if not possible=="":
					reobj = re.search ("^(\w+)\s+(\d+)\s+([\w\/\-\.]+)\s+(.+)$", possible)
					if reobj:
						self.__completionlist.append ( (reobj.group(1), reobj.group(2), reobj.group(3), reobj.group(4)) )
			code = pipe.close()
			if not code==None:
				raise "GlobalExecutionError", os.strerror(code)
				return []
			if len(self.__completionlist) == 0:
				again = again - 1
			else:
				break
		return self.__completionlist
#####################################################################

#####################################################################
# Remove script from paned book and all menu entries. Callback for close button.
#####################################################################
def global_destroy(button, citem, container):
	textw = glimmer.get_text_widget (glimmer.get_file_number())
	gtk_widget_grab_focus (textw)
	citem.destroy()
	_gnomeui.gnome_app_remove_menu_range (glimmer.get_gnome_app(), "_Search/", 11, 1)
	glimmer.remove_paned_object ("Global")
	return TRUE
#####################################################################

#####################################################################
# Callback for search button. Calls Tglobal_interface instance.
#####################################################################
def fetch_data(caller, global_if_inst, symbol_entry, symbols_clist, symbol_togglebutton):
	"Call Tglobal_interface.symbols or Tglobal_interface.tags and update list"

	if glimmer.has_selection ():
		selected = glimmer.get_text (glimmer.selection_start(),glimmer.selection_end ())
		selected = string.split(selected).pop()
		tag = re.search ("(\w+$)", selected).group(1)
		fromtext = 1
	elif re.search ("^\w+.*", symbol_entry.get_text()) == None:
		#No text defined, find the hint of function to search for
		selected = glimmer.get_text (glimmer.line_start(),glimmer.current_position ())
		selected = string.split(selected).pop()
		tag = re.search ("(\w+$)", selected).group(1)
		fromtext = 1
	else:
		tag = string.strip (symbol_entry.get_text ())

	if tag == "":
		return FALSE
	project_path = global_if_inst.project_path()
	try:
		if symbol_togglebutton.get_active():
			completionlist=global_if_inst.tags (tag)
		else:
			completionlist=global_if_inst.symbols (tag)
	except "GlobalExecutionError", strerror:
		GnomeErrorDialog ("Error executing global:\n"+strerror).run()
		return FALSE
	else :
		if len(completionlist) == 0:
			return FALSE
		for (functionname, linenumber, filename, details) in completionlist:
			filename = string.replace (filename, project_path, '')
			if os.path.isabs(filename):
				filename = filename[1:]
			symbols_clist.append ([linenumber, filename, details])
		symbols_clist.grab_focus ()
#####################################################################

#####################################################################
# Callback for tag list selection. Opens a file and searches for selected tag.
#####################################################################
def selection_made(caller, rowclicked, columnclicked, event, projpath_entry):
	"Row clicked in main list"
	projpath = projpath_entry.get_text()
	line = caller.get_text (rowclicked, 0)
	file = caller.get_text (rowclicked, 1)
	details = caller.get_text (rowclicked, 2)

	fullfile = os.path.join (projpath, file)
	if not os.path.exists (fullfile):
		return
	for nfile in range(glimmer.get_files()):
		openfile = glimmer.get_full_filename (nfile)
		if not os.path.exists (openfile):
			return
		if os.path.samefile (fullfile, openfile):
			#File already opened, go to the right line
			glimmer.change (nfile)
			glimmer.move_to_line (int(line))
			glimmer.move_to(glimmer.line_start())
			textw = glimmer.get_text_widget (nfile)
			gtk_widget_grab_focus (textw)
			return
	#File not opened, do it
	glimmer.open_file (fullfile)
	glimmer.move_to_line (int(line))
	glimmer.move_to(glimmer.line_start())
	textw = glimmer.get_text_widget (glimmer.get_file_number())
	gtk_widget_grab_focus (textw)
#####################################################################

#####################################################################
# View menu handling
#####################################################################
def toggle_global (caller, checkmenuitem, fbox):
	"Used from View->Global interface"
	if checkmenuitem.active:
		fbox.show()
	else:
		fbox.hide()
	return TRUE
def hide_global (caller, checkmenuitem, fbox):
	"Used from Hide button"
	checkmenuitem.set_active(FALSE)
	fbox.hide()
	return TRUE
#####################################################################

#####################################################################
# Project path selection routines. Callback for browse button and fileselector buttons.
#####################################################################
def browseprojpath_run (caller, projpath_entry):
	projpath_filesel = GtkFileSelection ("Select project directory")
	projpath_filesel.set_modal (TRUE)
	projpath_filesel.show ()
	projpath_filesel.ok_button.connect ("clicked", projpath_selection_ok, projpath_filesel, projpath_entry)
	projpath_filesel.cancel_button.connect ("clicked", projpath_selection_cancel, projpath_filesel)
	projpath_filesel.connect ("destroy", projpath_selection_cancel, projpath_filesel)
	gtk_main ()
def projpath_selection_ok (caller, filesel, update_entry):
	"Selected directory/file"
	dirname = os.path.dirname (filesel.get_filename())
	os.chdir (dirname)
	update_entry.set_text (dirname)
	filesel.destroy ()
	gtk_main_quit ()
def projpath_selection_cancel (caller, filesel):
	"Abort path selection"
	filesel.destroy ()
	gtk_main_quit ()
#####################################################################

#####################################################################
# Do not want to insert the proposed completion. Callback for completion list close button.
#####################################################################
def complete_selection_cancel (caller, window, symbol_entry):
	symbol_entry.set_text ('')
	window.destroy()
#####################################################################

#####################################################################
# Insert the proposed completion. Callback for completion list selection.
#####################################################################
def complete_selection_made (caller, rowclicked, columnclicked, event, symbol_entry, complete_win, fromtext):
	"Selected one of the available completions, insert into text if (fromtext)"
	selected = caller.get_text (rowclicked, 0)
	if fromtext == 0:
		symbol_entry.set_text (selected)
		complete_win.destroy ()
		return TRUE
	else:
		hint = symbol_entry.get_text ()
		print hint
		#Erase the hint we got
		glimmer.delete_text (glimmer.current_position()-len(hint), glimmer.current_position())
		glimmer.insert (selected)
		symbol_entry.set_text ('')
		complete_win.destroy ()
		return TRUE
#####################################################################

#####################################################################
# Popup a completion list. Callback for complete button.
#####################################################################
def search_winpopup (caller, global_if_inst, symbol_entry):
	"calls Tglobal_interface.completions to retrieve list of functions, displays them in a popup"
	fromtext = 0
	
	if glimmer.has_selection ():
		selected = glimmer.get_text (glimmer.selection_start(),glimmer.selection_end ())
		selected = string.split(selected).pop()
		function = re.search ("(\w+$)", selected).group(1)
		fromtext = 1
	elif re.search ("^\w+.*", symbol_entry.get_text()) == None:
		#No text defined, find the hint of function to search for
		selected = glimmer.get_text (glimmer.line_start(),glimmer.current_position ())
		selected = string.split(selected).pop()
		function = re.search ("(\w+$)", selected).group(1)
		fromtext = 1
	else:
		function = string.strip (symbol_entry.get_text ())

	if function == "":
		return FALSE
	symbol_entry.set_text (function)

	try:
		completionlist=global_if_inst.completions (symbol_entry.get_text())
	except "GlobalExecutionError", strerror:
		GnomeErrorDialog ("Error executing global:\n"+strerror).run()
		return FALSE
	if len(completionlist)==0:
		if fromtext:
			symbol_entry.set_text ('')
		return FALSE
	complete_win = GtkWindow (WINDOW_POPUP)
	complete_win.set_usize (150, 150)
	cwin_vbox = GtkVBox (FALSE, 2)
	complete_clist = GtkCList (1)
	close_button = GtkButton ("Close")
	accel_group = GtkAccelGroup ()
	complete_win.set_position (WIN_POS_MOUSE)
	complete_scrolledwin = GtkScrolledWindow (None, None)
	complete_scrolledwin.set_policy (POLICY_AUTOMATIC, POLICY_AUTOMATIC)
	complete_scrolledwin.set_border_width (0)
	complete_clist.set_column_width (0, 250)
	complete_clist.clear ()
	complete_clist.set_shadow_type (SHADOW_NONE)
	for stringa in completionlist:
		complete_clist.append ([stringa])
	complete_win.set_border_width (2)
	complete_win.add (cwin_vbox)
	complete_scrolledwin.add (complete_clist)
	cwin_vbox.pack_start (complete_scrolledwin)
	cwin_vbox.pack_end (close_button, FALSE, FALSE, 0)
	close_button.add_accelerator ("clicked", accel_group, C, MOD1_MASK, ACCEL_VISIBLE)
	close_button.add_accelerator ("clicked", accel_group, Escape, 0, ACCEL_VISIBLE)
	complete_win.add_accel_group (accel_group)
	close_button.connect ("clicked", complete_selection_cancel, complete_win, symbol_entry)
	complete_clist.connect ("select_row", complete_selection_made, symbol_entry, complete_win, fromtext)
	complete_win.foreach (showall)
	complete_win.show ()
	complete_win.set_modal (TRUE)
	complete_clist.grab_focus ()
#####################################################################

#####################################################################
# Recursively shows all childs of a widget. I didn't know of gtk_widget_show_all  :-))
#####################################################################
def showall (widget):
	widget.show()
	if GTK_CHECK_TYPE(widget._o, GtkContainer.get_type()):
		widget.foreach(showall)
#####################################################################

#####################################################################
# Start of the main code
#####################################################################
total_vbox = GtkVBox (FALSE, 2)
header_hbox = GtkHBox (FALSE, 2)
global_label = GtkLabel ("Global interface\nby Giuseppe \"Cowo\" Corbelli")
close_button = GtkButton ("Close")
close_button.set_relief(RELIEF_HALF)
hide_button = GtkButton ("Hide")
hide_button.set_relief(RELIEF_HALF)
separator = GtkHSeparator()
symbol_frame = GtkFrame ("Symbol/Function")
symbol_vbox = GtkVBox (TRUE, 1)
symbol_hbox = GtkHBox (FALSE, 2)
symbol_entry = GtkEntry()
if glimmer.has_selection():
	symbol_entry.set_text (glimmer.get_text(glimmer.selection_start(), glimmer.selection_end()))
symbol_togglebutton = GtkToggleButton ("Funcs only")
search_button = GtkButton ("Search")
search_button.set_relief (RELIEF_HALF)
complete_button = GtkButton ("Complete")
complete_button.set_relief (RELIEF_HALF)
projpath_frame = GtkFrame ("Project path")
projpath_vbox = GtkVBox (TRUE, 1)
projpath_hbox = GtkHBox (FALSE, 2)
projpath_entry = GtkEntry()
updateglobal_button = GtkButton ("Update GTAGS")
browseprojpath_button = GtkButton ("Browse")
browseprojpath_button.set_relief (RELIEF_HALF)
clearlist_button = GtkButton ("Clear List")
clearlist_button.set_relief (RELIEF_HALF)
symbols_scrolledwin = GtkScrolledWindow (None, None)
symbols_scrolledwin.set_policy (POLICY_ALWAYS, POLICY_ALWAYS)
symbols_clist = GtkCList (3, ["Line", "File", "Details"])
symbols_clist.set_selection_mode (SELECTION_SINGLE)
symbols_clist.set_column_width (1, 100)
symbols_clist.set_column_width (2, 500)
symbols_clist.column_title_passive (0)
symbols_clist.column_title_passive (1)
symbols_clist.column_title_passive (2)
globaltoggle_checkitem = GtkCheckMenuItem("Global interface")
globaltoggle_checkitem.set_active(TRUE)

#####################################################################
# Customizing View and Search menus
#####################################################################
globaltoggle_checkitem.show()
glimmer.add_widget_to_menu("_View/Status Bar", globaltoggle_checkitem._o)
#Add global menu section
glimmer.add_sub_to_menu ("_Search/", "Use global", 12)
glimmer.add_item_to_menu ("_Search/Use global/", "Complete", "Try to complete text using global", '', 1, complete_button.clicked)
glimmer.add_item_to_menu ("_Search/Use global/", "Search tag/symbol", "Search tag/symbol using global", '', 2, search_button.clicked)
#####################################################################

#####################################################################
#Packing section
#####################################################################
total_vbox.pack_start (header_hbox, FALSE, FALSE, 2)
header_hbox.pack_start (close_button, FALSE, FALSE, 2)
header_hbox.pack_start (global_label, TRUE, TRUE, 2)
header_hbox.pack_start (hide_button, FALSE, FALSE, 2)
total_vbox.pack_start (separator, FALSE, FALSE, 1)
total_vbox.pack_start (symbol_frame, FALSE, FALSE, 2)
symbol_frame.add (symbol_vbox)
symbol_vbox.pack_start (symbol_entry, TRUE, TRUE, 2)
symbol_vbox.pack_end (symbol_hbox, TRUE, TRUE, 2)
symbol_hbox.pack_start (symbol_togglebutton, TRUE, FALSE, 2)
symbol_hbox.pack_start (complete_button, TRUE, FALSE, 2)
symbol_hbox.pack_start (search_button, TRUE, FALSE, 2)
total_vbox.pack_start (projpath_frame, FALSE, FALSE, 2)
projpath_frame.add (projpath_vbox)
projpath_vbox.pack_start (projpath_entry, TRUE, TRUE, 2)
projpath_vbox.pack_end (projpath_hbox, TRUE, TRUE, 2)
projpath_hbox.pack_start (browseprojpath_button, TRUE, FALSE, 2)
projpath_hbox.pack_end (updateglobal_button, TRUE, FALSE, 2)
projpath_hbox.pack_end (clearlist_button, TRUE, FALSE, 2)
total_vbox.pack_start (symbols_scrolledwin, TRUE, TRUE, 2)
symbols_scrolledwin.add (symbols_clist)
gtk_widget_show_all (total_vbox._o)
glimmer.add_paned_object(total_vbox._o, "Global", 0)
#####################################################################

browseprojpath_button.connect ("clicked", browseprojpath_run, projpath_entry)

#####################################################################
# Must have a valid path before activating gui components
#####################################################################
global_interface = None
while global_interface == None: 
	try:
		global_interface = Tglobal_interface (os.path.join(os.getcwd()))
	except "GlobalNotFound", strerror:
		GnomeErrorDialog ("ERROR!!!\n"+strerror+"\n"+os.environ["PATH"]).run()
		close_button.clicked()
	except ("GlobalNoSuchDirectory", "GlobalNoGTAGSFile"), strerror:
		GnomeErrorDialog ("ERROR!!!\n"+strerror).run()
		symbol_entry.set_sensitive (FALSE)
		symbol_togglebutton.set_sensitive (FALSE)
		search_button.set_sensitive (FALSE)
		complete_button.set_sensitive (FALSE)
		updateglobal_button.set_sensitive (FALSE)
		symbols_scrolledwin.set_sensitive (FALSE)
		browseprojpath_button.clicked ()
	else:
		symbol_entry.set_sensitive (TRUE)
		symbol_togglebutton.set_sensitive (TRUE)
		search_button.set_sensitive (TRUE)
		complete_button.set_sensitive (TRUE)
		updateglobal_button.set_sensitive (TRUE)
		symbols_scrolledwin.set_sensitive (TRUE)
		projpath_entry.set_text (os.path.join(os.getcwd()))
#####################################################################

symbol_entry.grab_focus ()
#Signals
total_vbox.connect ("destroy", global_destroy, globaltoggle_checkitem, total_vbox)
close_button.connect ("clicked", global_destroy, globaltoggle_checkitem, total_vbox)
hide_button.connect ("clicked", hide_global, globaltoggle_checkitem, total_vbox)
globaltoggle_checkitem.connect ("toggled", toggle_global, globaltoggle_checkitem, total_vbox)
complete_button.connect ("clicked", search_winpopup, global_interface, symbol_entry)
search_button.connect ("clicked", fetch_data, global_interface, symbol_entry, symbols_clist, symbol_togglebutton)
symbol_entry.connect ("activate", fetch_data, projpath_entry, symbol_entry, symbols_clist, symbol_togglebutton)
symbols_clist.connect ("select_row", selection_made, projpath_entry)
clearlist_button.connect ("clicked", symbols_clist.clear)
updateglobal_button.connect ("clicked", global_interface.update_gtags)

