#!/usr/bin/env python
# coding: UTF-8
"""
Copyright (c) 2008 Marian Tietz
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
	notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
	notice, this list of conditions and the following disclaimer in the
	documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
"""


""" Purpose:
	setup and usage of submodules,
	main window signal handling,
	tekka internal signal handling,
	glue of all submodules
"""

import pygtk
pygtk.require("2.0")

import sys
import traceback

import gtk # TODO: catch gtk.Warning after init with warnings module

import os
import gobject
import pango
import dbus
import webbrowser
import locale
import types as ptypes
import logging

import gettext
from gettext import gettext as _

import gui
import gui.tabs

# local modules
from . import config
from . import com
from . import signals
from . import commands
from . import plugins

from .typecheck import types

from .lib import nick_list_store
from .lib.inline_dialog import InlineMessageDialog
from .lib.welcome_window import WelcomeWindow
from .lib.general_output_buffer import GOHTMLBuffer

from .helper import tabcompletion
from .helper import markup
from .helper.URLHandler import URLHandler

from .menus import *

import gui.builder
import gui.shortcuts

"""
Tekka intern signals
"""

def sushi_error_cb(sushi, title, message):
	""" Error in sushi interface occured. """

	def response_cb(d, i):
		gui.status.unset(title)
		d.destroy()

	d = InlineMessageDialog(title, message)
	d.connect("response", response_cb)
	gui.mgmt.show_inline_dialog(d)

	gui.status.set_visible(title, title)


def maki_connect_callback(sushi):
	""" connection to maki etablished """

	gui.mgmt.set_useable(True)


def maki_disconnect_callback(sushi):
	""" connection to maki lost """
	gui.mgmt.set_useable(False)


def tekka_server_new_nick_cb(tab, nick):
	""" New nick for the given tab. Apply the new nick in
		the GUI if the tab or a tab with the same server is active.
	"""

	activeTabs = gui.tabs.get_current_tabs()

	if (tab in activeTabs
	or (not tab.is_server() and tab.server in activeTabs)):
		gui.mgmt.set_nick(nick)


def tekka_tab_new_markup_cb(tab):
	""" Push the CellRenderer to re-render the serverTree """

	if not tab.path:
		return

	store = gui.widgets.get_object("tab_store")
	store[tab.path][0] = tab


def tekka_tab_new_message_cb(tab, mtype):
	""" A new message of the given type was received.
		If the tab is active, reset the message buffer
		and scroll the tab's textview to bottom if
		auto scrolling is enabled for this window.
	"""

	if tab.is_active():
		tab.set_new_message(None)

		if tab.window.auto_scroll and mtype:
			if tab.window.textview.is_smooth_scrolling():
				tab.window.textview.stop_scrolling()
				tab.window.textview.scroll_to_bottom(no_smooth = True)
			else:
				tab.window.textview.scroll_to_bottom()

	else:
		pass


def tekka_tab_new_name_cb(tab, name):
	tekka_tab_new_markup_cb(tab)


def tekka_tab_server_connected_cb(tab, connected):
	""" the server of the tab connected/disconnected """

	if tab.is_active():
		tab.set_useable(connected)


def tekka_channel_joined_cb(tab, switch):
	""" channel received a change on joined attribute """

	if tab.is_active():
		tab.set_useable(switch)


def tekka_tab_switched_cb(old, new):
	""" switched from tab old to tab new """

	inputBar = gui.widgets.get_object("input_entry")

	if old:
		itext = inputBar.get_text()
		old.set_input_text(itext)
		old.window.textview.set_read_line()

	inputBar.set_text("")
	inputBar.set_position(1)

	if new:
		inputBar.set_text(new.get_input_text())
		inputBar.set_position(len(inputBar.get_text()))

		if new.window.auto_scroll:
			# XXX: Needs testing!
			def check_for_scrolling():
				sw = new.window
				adj = sw.get_vadjustment()

				if adj.get_value() != (adj.upper - adj.page_size):
					sw.textview.scroll_to_bottom( no_smooth = True )
				else:
					print "No need for scrolling!"
				return False

			gobject.idle_add(check_for_scrolling)


def tekka_tab_add_cb(tab):
	""" a tab is added """

	if gui.mgmt.is_welcome_screen():
		# FIXME: this is called often if the tab is not changed
		gui.mgmt.visibility.show_welcome_screen(False)


def tekka_tab_remove_cb(tab):
	""" a tab is about to be removed """

	if gui.tabs.get_current_tab() == tab:
		# switch to another tab

		if tab.is_server():
			# server and children are removed, choose
			# another server
			server = gui.tabs.get_next_server(tab)

			if server:
				tabs = gui.tabs.get_all_tabs(servers = [server.name])
				nextTab = tabs[0]
			else:
				nextTab = None
		else:
			nextTab = gui.tabs.get_next_tab(tab)

		if None == nextTab:
			# lock interface
			# XXX:  maybe the inputBar should
			# XXX:: useable, though.
			gui.mgmt.set_useable(False)
		else:
			nextTab.switch_to()

	elif (tab.is_server()
	and len(gui.widgets.get_object("tab_store")) == 1):
		gui.mgmt.set_useable(False)


def tekka_channel_topic_changed_cb(tab, topic):
	if not tab.is_active(): return

	if (config.get_bool("tekka","hide_topic_if_empty")
	and config.get_bool("tekka", "show_topic_bar")):
		if topic:
			gui.mgmt.visibility.show_topic_bar(True)
		else:
			gui.mgmt.visibility.show_topic_bar(False)


def mainWindow_scroll_event_cb(mainWindow, event):
	""" MOD1 + SCROLL_DOWN -> Next tab
		MOD1 + SCROLL_UP -> Prev. tab
	"""

	if (event.state & gtk.gdk.MOD1_MASK
	and event.direction == gtk.gdk.SCROLL_DOWN):
		gui.tabs.switch_to_next()

	elif (event.state & gtk.gdk.MOD1_MASK
	and event.direction == gtk.gdk.SCROLL_UP):
		gui.tabs.switch_to_previous()


def mainWindow_delete_event_cb(mainWindow, event):
	"""
		If hide_on_close and the status icon are enabled,
		hide the main window. Otherwise stop the main loop.

		On hide, a read-line will be inserted in every tab.
	"""

	statusIcon = gui.widgets.get_object("status_icon")

	if (config.get_bool("tekka", "hide_on_close")
	and statusIcon and statusIcon.get_visible()):

		for tab in gui.tabs.get_all_tabs():
			tab.window.textview.set_read_line()

		mainWindow.hide()

		return True

	else:
		gtk.main_quit()


def mainWindow_focus_in_event_cb(mainWindow, event):
	"""
		Reset urgent status (if given)
	"""

	gui.mgmt.set_urgent(False)
	return False


def mainWindow_size_allocate_cb(mainWindow, alloc):
	""" Main window was resized, store the new size in the config. """

	if not mainWindow.window.get_state() & gtk.gdk.WINDOW_STATE_MAXIMIZED:
		config.set("sizes","window_width",alloc.width)
		config.set("sizes","window_height",alloc.height)


def mainWindow_window_state_event_cb(mainWindow, event):
	""" Window state was changed.
		If it's maximized or unmaximized, save that state.
	"""

	if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
		config.set("tekka","window_maximized","True")
	else:
		config.set("tekka","window_maximized","False")


def inputBar_activate_cb(inputBar):
	""" Enter hit, pass the inputBar text over to the
		commands.parseInput method and add the text to the input history.

		The inputBar is cleared after that.
	"""

	text = inputBar.get_text()

	tab = gui.tabs.get_current_tab()

	inputBar.set_text("")

	commands.parseInput(text)

	if tab:
		tab.input_history.add_entry(text)
		tab.input_history.reset()


def inputBar_key_press_event_cb(inputBar, event):
	""" Up -> Input history previous entry
		Down -> Input history next entry
		Tab -> Completion of the current word

		Everything else than tab ->
			No further completion wished, tell that.
	"""

	key =  gtk.gdk.keyval_name(event.keyval)
	tab =  gui.tabs.get_current_tab()

	text = unicode(inputBar.get_text(), "UTF-8")

	if key == "Up":
		# get next input history item
		if not tab:
			return

		hist = tab.input_history.get_previous()

		if hist != None:
			inputBar.set_text(hist)
			inputBar.set_position(len(hist))

	elif key == "Down":
		# get previous input history item
		if not tab:
			return

		hist = tab.input_history.get_next()

		if hist == None:
			return

		inputBar.set_text(hist)
		inputBar.set_position(len(hist))

	elif key == "Tab":
		# tab completion comes here.

		tabcompletion.complete(tab, inputBar, text)
		return True

	if key != "Tab":
		tabcompletion.stopIteration()


def notificationWidget_remove_cb(area, widget):
	""" restore the focus if a inline dialog is closed """
	gui.widgets.get_object("input_entry").grab_focus()


def outputShell_widget_changed_cb(shell, old_widget, new_widget):
	""" old_widget: OutputWindow
		new_widget: OutputWindow

		Set the current content of the output_shell in widgets store.
		- output_window <- new_widget
		- output        <- new_widget.textview
	"""
	if (type(old_widget) == WelcomeWindow
	and type(new_widget) != WelcomeWindow):
		gui.mgmt.visibility.show_welcome_screen(False)

	gui.widgets.remove_object("output_window")
	gui.widgets.add_object(new_widget, "output_window")

	gui.widgets.remove_object("output")
	gui.widgets.add_object(new_widget.textview, "output")


def serverTree_misc_menu_reset_activate_cb(menuItem):
	""" reset the markup of all tabs """

	for tab in gui.tabs.get_all_tabs():
		tab.set_new_message(None)


def serverTree_button_press_event_cb(serverTree, event):
	"""
		A row in the server tree was activated.
		The main function of this method is to
		cache the current activated row as path.
	"""

	try:
		path = serverTree.get_path_at_pos(int(event.x),int(event.y))[0]
		tab = serverTree.get_model()[path][0]
	except Exception as e:
		tab = None

	if event.button == 1:
		# activate the tab

		if tab:
			gui.tabs.switch_to_path(path)

	elif event.button == 2:
		# if there's a tab, ask to close
		if tab:
			askToRemoveTab(tab)

	elif event.button == 3:
		# popup tab menu

		if tab:
			menu = servertree_menu.ServerTreeMenu().get_menu(tab)

			if not menu:
				logging.error("error in creating server tree tab menu.")
				return False

			else:
				menu.popup(None, None, None, event.button, event.time)
				return True

		else:
			# display misc. menu
			menu = gtk.Menu()
			reset = gtk.MenuItem(label=_(u"Reset markup"))
			reset.connect("activate",
				serverTree_misc_menu_reset_activate_cb)
			menu.append(reset)
			reset.show()
			menu.popup(None,None,None,event.button,event.time)

	return False


def serverTree_row_activated_cb(serverTree, path, column):
	""" open the history dialog for the pointed tab """
	model = serverTree.get_model()
	tab = model[path][0]

	# don't show the history dialog for server tabs, they don't
	# have a history.
	if type(tab) != gui.tabs.TekkaServer:
		gui.dialogs.show_dialog("history", tab)


def nickList_row_activated_cb(nickList, path, column):
	"""
		The user activated a nick in the list.

		If there's a nick in the row a query
		for the nick on the current server will be opened.
	"""
	serverTab,channelTab = gui.tabs.get_current_tabs()

	try:
		name = nickList.get_model()[path][nick_list_store.COLUMN_NICK]
	except TypeError:
		# nickList has no model
		return
	except IndexError:
		# path is invalid
		return

	if gui.tabs.search_tab(serverTab.name, name):
		# already a query open
		return

	query = gui.tabs.create_query(serverTab, name)
	query.connected = True

	gui.tabs.add_tab(serverTab, query)

	query.print_last_log()
	query.switch_to()


def nickList_button_press_event_cb(nickList, event):
	"""
		A button pressed inner nickList.

		If it's the right mouse button and there
		is a nick at the coordinates, pop up a menu
		for setting nick options.
	"""
	if event.button == 3:
		# right mouse button pressed.

		path = nickList.get_path_at_pos(int(event.x), int(event.y))

		nick = None

		# get marked nick
		try:
			nick = nickList.get_model()[path[0]]
		except TypeError:
			# no model
			pass
		except IndexError:
			# path is "invalid"
			pass

		if nick:
			# display nick specific menu

			nick = nick[nick_list_store.COLUMN_NICK]

			menu = nicklist_menu.NickListMenu().get_menu(nick)

			if not menu:
				return False

			# finaly popup the menu
			menu.popup(None, None, None, event.button, event.time)

	return False


def nicks_view_query_tooltip_cb(view, x, y, kbdmode, tooltip):
	""" generate a tooltip with the awaymessage of the
		nick at the given x/y coordinates.
	"""

	# TODO: would be nice to have ident string of the nick here

	cursor = view.get_path_at_pos(x, y)

	if not cursor:
		return

	user_row = view.get_model()[cursor[0]]
	tip = ""

	# away message appendix
	if user_row[nick_list_store.COLUMN_AWAY]:
		# the user is away

		(server,_) = gui.tabs.get_current_tabs()


		if server:

			"""
			msg = com.sushi.awaymessage(server.name,
								user_row[nick_list_store.COLUMN_NICK])
			"""
			# TODO: retrieve awaymessage
			pass



""" Shortcut callbacks """

def inputBar_shortcut_ctrl_u(inputBar, shortcut):
	""" Ctrl + U was hit, clear the inputBar """

	gui.widgets.get_object("input_entry").set_text("")


def output_shortcut_ctrl_l(inputBar, shortcut):
	"""
		Ctrl+L was hit, clear the outputs.
	"""
	gui.mgmt.clear_all_outputs()


def output_shortcut_ctrl_f(inputBar, shortcut):
	""" show/hide the search toolbar """
	sb = gui.widgets.get_object("output_searchbar")

	if sb.get_property("visible"):
		sb.hide()
		gui.widgets.get_object("input_entry").grab_focus()
	else:
		sb.show_all()
		sb.grab_focus()


def output_shortcut_ctrl_g(inputBar, shortcut):
	""" search further """

	gui.widgets.get_object("output_searchbar").search_further()


def serverTree_shortcut_ctrl_Page_Up(serverTree, shortcut):
	""" Ctrl+Page_Up was hit, go up in server tree """

	gui.tabs.switch_to_previous()


def serverTree_shortcut_ctrl_Page_Down(serverTree, shortcut):
	""" Ctrl+Page_Down was hit, go down in server tree """

	gui.tabs.switch_to_next()


def askToRemoveTab(tab):
	def response_handler(dialog, response_id):

		if response_id == gtk.RESPONSE_YES:

			if tab.is_channel():
				com.sushi.part(tab.server.name, tab.name,
					config.get("chatting", "part_message", ""))

			elif tab.is_server():
				com.sushi.quit(tab.name,
					config.get("chatting", "quit_message", ""))

			gui.tabs.remove_tab(tab)

		dialog.destroy()

	if tab.is_channel():
		message = _(u"Do you really want to close channel “%(name)s”?")
	elif tab.is_query():
		message = _(u"Do you really want to close query “%(name)s”?")
	elif tab.is_server():
		message = _(u"Do you really want to close server “%(name)s”?")

	dialog = InlineMessageDialog(
		message % { "name": tab.name },
		icon=gtk.STOCK_DIALOG_QUESTION,
		buttons=gtk.BUTTONS_YES_NO
	)
	dialog.connect("response", response_handler)

	gui.mgmt.show_inline_dialog(dialog)


def serverTree_shortcut_ctrl_w(serverTree, shortcut):
	""" Ctrl+W was hit, close the current tab (if any) """

	tab = gui.tabs.get_current_tab()

	if not tab:
		return

	askToRemoveTab(tab)


def output_shortcut_Page_Up(inputBar, shortcut):
	"""
		Page_Up was hit, scroll up in output
	"""
	vadj = gui.widgets.get_object("output_window").get_vadjustment()

	if vadj.get_value() == 0.0:
		return # at top already

	n = vadj.get_value()-vadj.page_size
	if n < 0: n = 0
	gobject.idle_add(vadj.set_value,n)


def output_shortcut_Page_Down(inputBar, shortcut):
	""" Page_Down was hit, scroll down in output """

	vadj = gui.widgets.get_object("output_window").get_vadjustment()

	if (vadj.upper - vadj.page_size) == vadj.get_value():
		return # we are already at bottom

	n = vadj.get_value()+vadj.page_size

	if n > (vadj.upper - vadj.page_size):
		n = vadj.upper - vadj.page_size

	gobject.idle_add(vadj.set_value,n)


def inputBar_shortcut_ctrl_c(inputBar, shortcut):
	"""
		Ctrl + C was hit.
		Check every text input widget for selection
		and copy the selection to clipboard.
		FIXME: this solution sucks ass.
	"""
	buffer = gui.widgets.get_object("output").get_buffer()
	goBuffer = gui.widgets.get_object("general_output").get_buffer()
	topicBar = gui.widgets.get_object("topic_label")
	cb = gtk.Clipboard()

	if buffer.get_property("has-selection"):
		buffer.copy_clipboard(cb)
	elif inputBar.get_selection_bounds():
		inputBar.copy_clipboard()
	elif goBuffer.get_property("has-selection"):
		goBuffer.copy_clipboard(cb)
	elif topicBar.get_selection_bounds():
		bounds = topicBar.get_selection_bounds()
		text = unicode(topicBar.get_text(), "UTF-8")
		text = text[bounds[0]:bounds[1]]
		cb.set_text(text)


def changeTopic_shortcut(inputBar, shortcut):
	""" The user wants to change the topic for the current tab.
	"""
	channel = gui.tabs.get_current_tab()
	if not channel or not channel.is_channel():
		return

	menu = servertree_menu.ServerTreeMenu()
	menu.get_menu(channel)
	menu.widgets.get_object("setTopicItem").activate()


def serverTree_query_tooltip_cb(widget, x, y, kbdmode, tooltip):
	""" show tooltips for treeview rows.

		Server tabs:
			Nick: <nickname>

		Channel tabs:
			Users: <count>
			Topic: <topic>
			Last Sentence: <last sentence>

		Query tabs:
			Last Sentence: <last sentence>
	"""

	def limit(s):
		limit = int(config.get("tekka","popup_line_limit"))
		if len(s) > limit:
			return markup.escape(s[:limit-3]+u"...")
		return markup.escape(s)

	path = widget.get_path_at_pos(x,y)

	if not path:
		return

	path = path[0]

	try:
		tab = widget.get_model()[path][0]
	except IndexError:
		return

	if tab.is_server():
		# TODO: away status
		s = "<b>" + _("Nickname: ") + "</b>" +  markup.escape(tab.nick)

	elif tab.is_channel():
		s = "<b>" +_("User: ") + "</b>" + str(len(tab.nickList)) +\
			"\n<b>" + _("Topic: ") + "</b>" +\
				limit(tab.topic) +\
			"\n<b>" + _("Last sentence: ") + "</b>" +\
				limit(tab.window.textview.get_last_line())

	elif tab.is_query():
		s = "<b>" + _("Last sentence: ") + "</b>" +\
			limit(tab.window.textview.get_last_line())

	tooltip.set_markup(s)

	return True


def serverTree_render_server_cb(column, renderer, model, iter):
	""" Renderer func for column "Server" in servertree """

	tab = model.get(iter, 0)

	if not tab or not tab[0]:
		return

	renderer.set_property("markup",tab[0].markup())


def nickList_render_nicks_cb(column, renderer, model, iter):
	""" Renderer func for column "Nicks" in NickList """

	if not com.sushi.connected:
		# do not render if no connection exists
		return

	# highlight own nick
	serverTab = gui.tabs.get_current_tabs()[0]

	if not serverTab:
		return

	nick = model.get(iter, 1)
	away = model.get(iter, 2)

	if not nick:
		return

	nick = nick[0]
	away = away[0]

	# highlight own nick
	if com.get_own_nick(serverTab.name) == nick:
		renderer.set_property("weight", pango.WEIGHT_BOLD)
	else:
		renderer.set_property("weight", pango.WEIGHT_NORMAL)

	if away:
		renderer.set_property("style", pango.STYLE_ITALIC)
	else:
		renderer.set_property("style", pango.STYLE_NORMAL)



def treemodel_rows_reordered_cb(treemodel, path, iter, new_order):
	""" new_order is not accessible, so hack arround it... """

	# explicit import because what we do is bad.
	# there should be no one writing on current_path
	from gui.tabs.current import _set_current_path

	updated = False
	for row in treemodel:
		if not row[0]:
			continue

		if gui.tabs.get_current_path() == row[0].path and not updated:
			# update the currentPath cache
			_set_current_path(row.path)
			updated = True

		# update the tab's path cache
		row[0].path = row.path

		for child in row.iterchildren():
			if not child[0]:
				continue

			if (gui.tabs.get_current_path() == child[0].path
			and not updated):
				_set_current_path(child.path)
				updated = True

			# update path's tab cache
			child[0].path = child.path


def paned_notify_cb(paned, gparam):
	if not paned_notify_cb.init_done:
		return

	if gparam.name == "position":

		# only save if there are no inline dialogs displayed
		ids = gui.widgets.get_object("notification_vbox").get_children()
		if len(ids) == 0:
			config.set("sizes", gtk.Buildable.get_name(paned),
						paned.get_property("position"))

paned_notify_cb.init_done = False


""" Initial setup routines """

def setup_main_window():
	"""
		- set window title
		- set window icon
		- set window size
		- set window state
	"""
	win = gui.widgets.get_object("main_window")

	win.set_title("tekka IRC client")

	if config.get_bool("tekka", "rgba"):
		colormap = win.get_screen().get_rgba_colormap()
		if colormap:
			gtk.widget_set_default_colormap(colormap)

	try:
		img = gtk.icon_theme_get_default().load_icon("tekka",64,0)

		win.set_icon(img)
	except gobject.GError:
		# file not found
		pass

	# Restore sizes from last start
	width = config.get("sizes","window_width")
	height = config.get("sizes","window_height")

	if width and height:
		win.resize(int(width),int(height))


	# Restore window state from last start
	if config.get_bool("tekka","window_maximized"):
		win.maximize()

	# enable scrolling through server tree by scroll wheel
	def kill_mod1_scroll(w,e):
		if e.state & gtk.gdk.MOD1_MASK:
			w.emit_stop_by_name("scroll-event")

	for widget in ("general_output_window",
				   "tabs_window",
				   "nicks_window"):
		gui.widgets.get_object(widget).connect("scroll-event",
											   kill_mod1_scroll)

	win.show()


def setup_tabs_view():
	""" Setup tab sorting, setup tab rendering """
	model = gui.widgets.get_object("tab_store")

	# Sorting
	def cmpl(m,i1,i2):
		" compare columns lower case "

		a = m.get_value(i1, 0)
		b = m.get_value(i2, 0)

		c, d = None, None

		if a: c=a.name.lower()
		if b: d=b.name.lower()
		return cmp(c,d)

	model.set_sort_func(1, lambda m,i1,i2,*x: cmpl(m,i1,i2))
	model.set_sort_column_id(1, gtk.SORT_ASCENDING)

	# Setup the renderer
	column = gui.widgets.get_object("tabs_view_name_column")
	column.set_cell_data_func(
				gui.widgets.get_object("tabs_view_name_renderer"),
				serverTree_render_server_cb)


def setup_general_ouptut():
	""" set the textview's buffer to a GOHTMLBuffer and add
		the textview as general_output to the widgets store
	"""
	w = gui.widgets.get_object("general_output_window")
	w.textview.set_buffer(GOHTMLBuffer(handler=URLHandler))
	gui.widgets.add_object(w.textview, "general_output")


def setup_nicks_view():
	""" setup custom rendering of nick column """
	column = gui.widgets.get_object("nicks_store_nick_column")
	column.set_cell_data_func(
			gui.widgets.get_object("nicks_store_nick_renderer"),
			nickList_render_nicks_cb)


def load_paned_positions():
	""" restore the positions of the
		paned dividers for the list,
		main and output paneds.
	"""

	paneds = [
		"list_vpaned",
		"main_hpaned",
		"output_vpaned"]

	for paned_name in paneds:
		paned = gui.widgets.get_object(paned_name)

		position = config.get("sizes", paned_name, None)
		paned.set_property("position-set", True)

		if position == None:
			continue

		try:
			paned.set_position(int(position))
		except ValueError:
			logging.error("Failed to set position for paned %s" % (
				paned.name))
			continue


def setup_paneds():
	load_paned_positions()
	paned_notify_cb.init_done = True
	return False


def setup_fonts():
	""" add default font callback """
	try:
		import gconf

		def default_font_cb (client, id, entry, data):
			if not config.get_bool("tekka", "use_default_font"):
				return

			gui.mgmt.apply_new_font()

		c = gconf.client_get_default()

		c.add_dir("/desktop/gnome/interface", gconf.CLIENT_PRELOAD_NONE)
		c.notify_add("/desktop/gnome/interface/monospace_font_name",
			default_font_cb)

	except:
		# ImportError or gconf reported a missing dir.
		pass


def setup_topic_label():
	""" tooltip style box arround topic label """

	def expose_event_cb(box, event):
		a = box.get_allocation()

		box.style.paint_flat_box(
			box.window,
			gtk.STATE_NORMAL,
			gtk.SHADOW_ETCHED_IN,
			None,
			box,
			"tooltip",
			a.x,
			a.y,
			a.width,
			a.height - 1
		)
		return False
	gui.widgets.get_object("topic_label").connect(
		"expose-event", expose_event_cb)



def setupGTK():
	""" Set locale, load UI file, connect signals, setup widgets. """

	uifiles = config.get("uifiles", default={})

	# setup locale stuff
	try:
		locale.setlocale(locale.LC_ALL, '')
		locale.bindtextdomain("tekka", config.get("tekka","locale_dir"))
		locale.textdomain("tekka")
	except:
		pass

	# Fix about dialog URLs
	def about_dialog_url_hook (dialog, link, data):
		if gtk.gtk_version >= (2, 16, 0): return
		webbrowser.open(link)
	gtk.about_dialog_set_url_hook(about_dialog_url_hook, None)

	# Fire gettext up with our locale directory
	gettext.bindtextdomain("tekka", config.get("tekka","locale_dir"))
	gettext.textdomain("tekka")

	# parse ui file for main window
	gui.builder.load_main_window(uifiles["mainwindow"])

	setup_main_window()


	mmc = gui.widgets.get_object("main_menu_context")

	# tell the searchbar where it can get it's input
	gui.widgets.get_object("output_searchbar").textview_callback =\
		lambda: gui.widgets.get_object("output")

	# connect tab control signals
	gui.tabs.add_callbacks({
		"new_message": tekka_tab_new_message_cb,
		"new_name": tekka_tab_new_name_cb,
		"add": tekka_tab_add_cb,
		"remove": tekka_tab_remove_cb,
		"new_markup": tekka_tab_new_markup_cb,
		"server_connected": tekka_tab_server_connected_cb,
		"joined": tekka_channel_joined_cb,
		"new_nick": tekka_server_new_nick_cb,
		"tab_switched": tekka_tab_switched_cb,
		"topic": tekka_channel_topic_changed_cb,
		})

	# connect main window signals:
	sigdic = {
		# main window signals
		"main_window_delete_event":
			mainWindow_delete_event_cb,
		"main_window_focus_in_event":
			mainWindow_focus_in_event_cb,
		"main_window_size_allocate":
			mainWindow_size_allocate_cb,
		"main_window_window_state_event":
			mainWindow_window_state_event_cb,
		"main_window_scroll_event":
			mainWindow_scroll_event_cb,

		# server tree signals
		"tabs_view_button_press_event" :
			serverTree_button_press_event_cb,
		"tabs_view_row_activated":
			serverTree_row_activated_cb,
		"tabs_view_query_tooltip":
			serverTree_query_tooltip_cb,

		# Store of the tabs view
		"tab_store_rows_reordered":
			treemodel_rows_reordered_cb,

		# Input entry...
		"input_entry_activate":
			inputBar_activate_cb,
		"input_entry_key_press_event":
			inputBar_key_press_event_cb,

		# Notification VBox
		"notification_vbox_remove":
			notificationWidget_remove_cb,

		"output_shell_widget_changed":
			outputShell_widget_changed_cb,

		# nick list signals
		"nicks_view_row_activated":
			nickList_row_activated_cb,
		"nicks_view_button_press_event":
			nickList_button_press_event_cb,
		"nicks_view_query_tooltip":
			nicks_view_query_tooltip_cb,

		# watch for position change of paneds
		"list_vpaned_notify":
			paned_notify_cb,
		"main_hpaned_notify":
			paned_notify_cb,
		"output_vpaned_notify":
			paned_notify_cb,

	# tekka menu context
		"tekka_server_list_item_activate":
			mmc.tekka.connect_activate_cb,
		"tekka_quit_item_activate":
			mmc.tekka.quit_activate_cb,
	# maki menu context
		"maki_connect_item_activate":
			mmc.maki.connect_activate_cb,
		"maki_disconnect_item_activate":
			mmc.maki.disconnect_activate_cb,
		"maki_shutdown_item_activate":
			mmc.maki.shutdown_activate_cb,
	# view menu context
		"view_general_output_item_toggled":
			mmc.view.showGeneralOutput_toggled_cb,
		"view_side_pane_item_toggled":
			mmc.view.showSidePane_toggled_cb,
		"view_status_bar_item_toggled":
			mmc.view.showStatusBar_toggled_cb,
		"view_status_icon_item_toggled":
			mmc.view.showStatusIcon_toggled_cb,
		"view_topic_bar_item_toggled":
			mmc.view.showTopicBar_toggled_cb,
	# tools menu context
		"tools_channel_list_item_activate":
			mmc.tools.channelList_activate_cb,
		"tools_file_transfers_item_activate" :
			mmc.tools.dcc_activate_cb,
		"tools_plugins_item_activate" :
			mmc.tools.plugins_activate_cb,
		"tools_debug_item_activate" :
			mmc.tools.debug_activate_cb,
		"tools_preferences_item_activate" :
			mmc.tools.preferences_activate_cb,
	# help menu context
		"help_irc_colors_item_activate":
			mmc.help.colors_activate_cb,
		"help_about_item_activate":
			mmc.help.about_activate_cb,
	}

	gui.widgets.connect_signals(sigdic)

	# push status messages directly in the status bar
	gui.status.connect("set-visible-status",
		lambda w,s,m: gui.widgets.get_object("statusbar")\
		.push(gui.status.id(s), m))

	# pop status message if they're unset
	gui.status.connect("unset-status",
		lambda w,s: gui.widgets.get_object("statusbar")\
		.pop(gui.status.id(s)))

	# initialize output_shell again (signals are connected now)
	gui.widgets.get_object("output_shell").reset()

	# setup more complex widgets
	setup_tabs_view()
	setup_nicks_view()
	setup_general_ouptut()
	setup_topic_label()

	# apply visibility to widgets from config
	mmc.view.apply_visibility_settings()
	gui.mgmt.visibility.apply_visibility_from_config()

	setup_fonts()

	# set input font
	gui.mgmt.set_font(gui.widgets.get_object("input_entry"),
					  gui.mgmt.get_font())

	# set general output font
	gui.mgmt.set_font(gui.widgets.get_object("general_output"),
					  gui.mgmt.get_font())

	gui.shortcuts.add_handlers({
			"clear_outputs": output_shortcut_ctrl_l,
			"output_page_up": output_shortcut_Page_Up,
			"output_page_down": output_shortcut_Page_Down,
			"input_clear_line": inputBar_shortcut_ctrl_u,
			"input_search": output_shortcut_ctrl_f,
			"input_search_further": output_shortcut_ctrl_g,
			"input_copy": inputBar_shortcut_ctrl_c,
			"change_topic": changeTopic_shortcut,
			"servertree_previous": serverTree_shortcut_ctrl_Page_Up,
			"servertree_next": serverTree_shortcut_ctrl_Page_Down,
			"servertree_close": serverTree_shortcut_ctrl_w,
			"show_sidepane": lambda w,s: w.set_active(not w.get_active()),
		})
	gui.shortcuts.setup_shortcuts()

	gui.mgmt.visibility.show_welcome_screen(True)

	# disable the GUI and wait for commands :-)
	gui.mgmt.set_useable(False)

	gobject.idle_add(setup_paneds)


def tekka_excepthook(extype, exobj, extb):
	""" we got an exception, print it in a dialog box and,
		if possible, to the standard output.
	"""

	message = "%s\n%s: %s\n" % (
		"".join(traceback.format_tb(extb)),
		extype.__name__,
		str(exobj))

	try:
		print >> sys.stderr, message
	except:
		pass

	self = tekka_excepthook

	def dialog_response_cb(dialog, rid):
		del self.dialog
		dialog.destroy()

	if not hasattr(self, "dialog"):
		from .lib.error_dialog import ErrorDialog

		self.dialog = ErrorDialog(message)
		self.dialog.connect("response", dialog_response_cb)
		self.dialog.show_all()
	else:
		self.dialog.set_message(message)

	sys.__excepthook__(extype, exobj, extb)


def setup_logging():
	""" set the path of the logfile to tekka.logfile config
		value and create it (including path) if needed.
		After that, add a logging handler for exceptions
		which reports exceptions catched by the logger
		to the tekka_excepthook. (DBus uses this)
	"""
	try:
		class ExceptionHandler(logging.Handler):
			""" handler for exceptions caught with logging.error.
				dump those exceptions to the exception handler.
			"""
			def emit(self, record):
				if record.exc_info:
					tekka_excepthook(*record.exc_info)

		logfile = config.get("tekka","logfile")
		logdir = os.path.dirname(logfile)

		if not os.path.exists(logdir):
			os.makedirs(logdir)

		logging.basicConfig(filename = logfile, level = logging.DEBUG,
			filemode="w")

		logging.getLogger("").addHandler(ExceptionHandler())

	except BaseException as e:
		print >> sys.stderr, "Logging init error: %s" % (e)


def setup():
	""" Setup the UI """

	# load config file, apply defaults
	config.setup()

	# create logfile, setup logging module
	setup_logging()

	# setup callbacks
	com.sushi.g_connect("sushi-error", sushi_error_cb)
	com.sushi.g_connect("maki-connected", maki_connect_callback)
	com.sushi.g_connect("maki-disconnected", maki_disconnect_callback)
	signals.setup()

	# build graphical interface
	setupGTK()

	# setup exception handler
	sys.excepthook = tekka_excepthook


def main(args):
	""" Fire up the UI """

	# connect to maki daemon
	com.connect()

	plugins.load_autoloads()

	# start main loop
	gtk.main()

	# after main loop break, write config
	config.write_config_file()

	# At last, close maki if requested
	if config.get_bool("tekka", "close_maki_on_close"):
		com.sushi.shutdown(config.get("chatting", "quit_message", ""))

