import rox
from rox import g, filer, app_options
import sys
from rox.loading import XDSLoader
from rox.options import Option
from buffer import Buffer
from rox.saving import Saveable

FALSE = g.FALSE
TRUE = g.TRUE

from rox.Menu import Menu, set_save_name

default_font = Option('default_font', 'serif')

background_colour = Option('background', '#fff')
foreground_colour = Option('foreground', '#000')

set_save_name('Edit')

menu = Menu('main', [
	('/File',		'',		'<Branch>'),
	('/File/Save',		'save',		''),
	('/File/Open Parent',	'up',		''),
	('/File/Close',		'close',	''),
	('/File/',		'',		'<Separator>'),
	('/File/New',		'new',		''),
	('/Edit',		'',		'<Branch>'),
	('/Edit/Undo',		'undo',		''),
	('/Edit/Redo',		'redo',		''),
	('/Edit/',		'',		'<Separator>'),
	('/Edit/Search...',	'search',	''),
	('/Edit/Goto line...',	'goto',		''),
	('/Edit/',		'',		'<Separator>'),
	('/Edit/Process...',	'process',	''),
	('/Options',		'show_options', ''),
	('/Help',		'help',		'',		'F1'),
	])

known_codecs = (
	"iso8859_1", "iso8859_2", "iso8859_3", "iso8859_4", "iso8859_5",
	"iso8859_6", "iso8859_7", "iso8859_8", "iso8859_9", "iso8859_10",
	"iso8859_13", "iso8859_14", "iso8859_15",
	"ascii", "base64_codec", "charmap",
	"cp037", "cp1006", "cp1026", "cp1140", "cp1250", "cp1251", "cp1252",
	"cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "cp1258", "cp424",
	"cp437", "cp500", "cp737", "cp775", "cp850", "cp852", "cp855", "cp856",
	"cp857", "cp860", "cp861", "cp862", "cp863", "cp864", "cp865", "cp866",
	"cp869", "cp874", "cp875", "hex_codec",
	"koi8_r",
	"latin_1",
	"mac_cyrillic", "mac_greek", "mac_iceland", "mac_latin2", "mac_roman", "mac_turkish",
	"mbcs", "quopri_codec", "raw_unicode_escape",
	"rot_13",
	"utf_16_be", "utf_16_le", "utf_16", "utf_7", "utf_8", "uu_codec",
	"zlib_codec"
)

class Abort(Exception):
	pass

class Minibuffer:
	def setup(self):
		"""Called when the minibuffer is opened."""
	
	def key_press(self, kev):
		"""A keypress event in the minibuffer text entry."""
	
	def changed(self):
		"""The minibuffer text has changed."""
	
	def activate(self):
		"""Return or Enter pressed."""
	
	info = 'Press Escape to close the minibuffer.'

class EditWindow(g.Window, XDSLoader, Saveable):
	def __init__(self, filename = None):
		g.Window.__init__(self)
		XDSLoader.__init__(self, ['text/plain', 'UTF8_STRING'])
		self.set_default_size(g.gdk.screen_width() * 2 / 3,
				      g.gdk.screen_height() / 2)

		self.savebox = None
		self.info_box = None

		app_options.add_notify(self.update_styles)

		self.buffer = Buffer()

		scrollbar = g.VScrollbar()
		self.text = g.TextView()
		self.text.set_property('left-margin', 4)
		self.text.set_property('right-margin', 4)
		self.text.set_buffer(self.buffer)
		adj = scrollbar.get_adjustment()
		self.text.set_scroll_adjustments(None, adj)
		self.text.set_size_request(10, 10)
		self.xds_proxy_for(self.text)
		self.text.set_wrap_mode(g.WRAP_WORD)
		self.update_styles()
		
		self.insert_mark = self.buffer.get_mark('insert')
		self.selection_bound_mark = self.buffer.get_mark('selection_bound')
		start = self.buffer.get_start_iter()
		self.mark_start = self.buffer.create_mark('mark_start', start, TRUE)
		self.mark_end = self.buffer.create_mark('mark_end', start, FALSE)
		tag = self.buffer.create_tag('marked')
		tag.set_property('background', 'green')
		self.marked = 0

		# When searching, this is where the cursor was when the minibuffer
		# was opened.
		start = self.buffer.get_start_iter()
		self.search_base = self.buffer.create_mark('search_base', start, TRUE)

		vbox = g.VBox(FALSE)
		self.add(vbox)

		tools = g.Toolbar()
		tools.set_style(g.TOOLBAR_ICONS)
		vbox.pack_start(tools, FALSE, TRUE, 0)
		tools.show()

		tools.insert_stock(g.STOCK_HELP, 'Help', None, self.help, None, 0)
		tools.insert_stock(g.STOCK_REDO, 'Redo', None, self.redo, None, 0)
		tools.insert_stock(g.STOCK_UNDO, 'Undo', None, self.undo, None, 0)
		tools.insert_stock(g.STOCK_FIND, 'Search', None, self.search, None, 0)
		tools.insert_stock(g.STOCK_SAVE, 'Save', None, self.save, None, 0)
		tools.insert_stock(g.STOCK_GO_UP, 'Up', None, self.up, None, 0)
		tools.insert_stock(g.STOCK_CLOSE, 'Close', None, self.close, None, 0)
		
		hbox = g.HBox(FALSE)		# View + Minibuffer + Scrollbar
		vbox.pack_start(hbox, TRUE, TRUE)

		inner_vbox = g.VBox(FALSE)	# View + Minibuffer
		hbox.pack_start(inner_vbox, TRUE, TRUE)
		inner_vbox.pack_start(self.text, TRUE, TRUE)
		hbox.pack_start(scrollbar, FALSE, TRUE)

		self.show_all()

		# Create the minibuffer
		self.mini_hbox = g.HBox(FALSE)
		info = g.Button()
		info.set_relief(g.RELIEF_NONE)
		info.unset_flags(g.CAN_FOCUS)
		image = g.Image()
		image.set_from_stock(g.STOCK_DIALOG_INFO, size = g.ICON_SIZE_SMALL_TOOLBAR)
		info.add(image)
		info.show_all()
		info.connect('clicked', self.mini_show_info)
		
		self.mini_hbox.pack_start(info, FALSE, TRUE, 0)
		self.mini_label = g.Label('')
		self.mini_hbox.pack_start(self.mini_label, FALSE, TRUE, 0)
		self.mini_entry = g.Entry()
		self.mini_hbox.pack_start(self.mini_entry, TRUE, TRUE, 0)
		inner_vbox.pack_start(self.mini_hbox, FALSE, TRUE)
		self.mini_entry.connect('key-press-event', self.mini_key_press)
		self.mini_entry.connect('changed', self.mini_changed)

		rox.toplevel_ref()
		self.connect('destroy', self.destroyed)

		self.connect('delete-event', self.delete_event)
		self.text.grab_focus()

		if filename:
			import os.path
			self.uri = os.path.abspath(filename)
		else:
			self.uri = None
		self.update_title()

		# Loading might take a while, so get something on the screen
		# now...
		g.gdk.flush()

		if filename:
			try:
				self.load_file(filename)
				if filename != '-':
					self.save_mode = os.stat(filename).st_mode
			except Abort:
				self.destroy()
				raise

		self.buffer.connect('modified-changed', self.update_title)
		self.buffer.set_modified(FALSE)

		self.text.connect('button-press-event', self.button_press)

		menu.attach(self, self)
		self.buffer.place_cursor(self.buffer.get_start_iter())
		self.buffer.start_undo_history()

	def destroyed(self, widget):
		app_options.remove_notify(self.update_styles)
		rox.toplevel_unref()
		
	def update_styles(self):
		try:
			import pango
			font = pango.FontDescription(default_font.value)
			bg = g.gdk.color_parse(background_colour.value)
			fg = g.gdk.color_parse(foreground_colour.value)
		except:
			rox.report_exception()
		else:
			self.text.modify_font(font)
			self.text.modify_base(g.STATE_NORMAL, bg)
			self.text.modify_text(g.STATE_NORMAL, fg)
	
	def button_press(self, text, event):
		if event.button != 3:
			return 0
		menu.popup(self, event)
		return 1

	def delete_event(self, window, event):
                if self.buffer.get_modified():
                        self.save(discard = 1)
                        return 1
                return 0

	def update_title(self, *unused):
		title = self.uri or '<Untitled>'
                if self.buffer.get_modified():
                        title = title + " *"
                self.set_title(title)

        def xds_load_from_stream(self, name, t, stream):
		if t == 'UTF8_STRING':
			return	# Gtk will handle it
		try:
			self.insert_data(stream.read())
		except Abort:
			pass
	
	def get_encoding(self, message):
		"Returns (encoding, errors), or raises Abort to cancel."
		import codecs

		box = g.MessageDialog(self, 0, g.MESSAGE_QUESTION, g.BUTTONS_CANCEL, message)
		box.set_has_separator(FALSE)

		frame = g.Frame()
		box.vbox.pack_start(frame, TRUE, TRUE)
		frame.set_border_width(6)

		hbox = g.HBox(FALSE, 4)
		hbox.set_border_width(6)

		hbox.pack_start(g.Label('Encoding:'), FALSE, TRUE, 0)
		combo = g.Combo()
		combo.disable_activate()
		combo.entry.connect('activate', lambda w: box.activate_default())
		combo.set_popdown_strings(known_codecs)
		hbox.pack_start(combo, TRUE, TRUE, 0)
		ignore_errors = g.CheckButton('Ignore errors')
		hbox.pack_start(ignore_errors, FALSE, TRUE)

		frame.add(hbox)
		
		box.vbox.show_all()
		box.add_button(g.STOCK_CONVERT, g.RESPONSE_YES)
		box.set_default_response(g.RESPONSE_YES)

		while 1:
			combo.entry.grab_focus()
			
			resp = box.run()
			if resp != g.RESPONSE_YES:
				box.destroy()
				raise Abort

			if ignore_errors.get_active():
				errors = 'replace'
			else:
				errors = 'strict'
			encoding = combo.entry.get_text()
			try:
				codecs.getdecoder(encoding)
				break
			except:
				rox.alert("Unknown encoding '%s'" % encoding)
			
		box.destroy()

		return encoding, errors
	
	def insert_data(self, data):
		import codecs
		errors = 'strict'
		encoding = 'utf-8'
		while 1:
			decoder = codecs.getdecoder(encoding)
			try:
				data = decoder(data, errors)[0]
				assert '\0' not in data
				break
			except:
				pass

			encoding, errors = self.get_encoding(
				"Data is not valid %s. Please select the file's encoding."
				"Turn on 'ignore errors' to try and load it anyway." % encoding)

		self.buffer.insert_at_cursor(data, -1)
		return 1
	
	def load_file(self, path):
		try:
			if path == '-':
				file = sys.stdin
			else:
				file = open(path, 'r')
			contents = file.read()
			if path != '-':
				file.close()
			self.insert_data(contents)
		except Abort:
			raise
		except:
			rox.report_exception()
			raise Abort
	
	def close(self, button = None):
                if self.buffer.get_modified():
                        self.save(discard = 1)
                else:
                        self.destroy()

	def discard(self):
                self.destroy()

        def up(self, button = None):
                if self.uri:
                        filer.show_file(self.uri)
                else:
                        rox.alert("File is not saved to disk yet")

	def has_selection(self):
		s, e = self.get_selection_range()
		return not e.equal(s)
	
	def get_marked_range(self):
		s = self.buffer.get_iter_at_mark(self.mark_start)
		e = self.buffer.get_iter_at_mark(self.mark_end)
		return s, e

	def get_selection_range(self):
		s = self.buffer.get_iter_at_mark(self.insert_mark)
		e = self.buffer.get_iter_at_mark(self.selection_bound_mark)
		return s, e

	def save(self, widget = None, discard = 0):
		from rox.saving import SaveBox

		if self.savebox:
			self.savebox.destroy()

		if self.has_selection() and not discard:
			saver = SelectionSaver(self)
			self.savebox = SaveBox(saver, 'Selection', 'text/plain')
			self.savebox.connect('destroy', lambda w: saver.destroy())
		else:
			uri = self.uri or 'TextFile'
			self.savebox = SaveBox(self, uri, 'text/plain', discard)
		self.savebox.show()

        def help(self, button = None):
                filer.open_dir(rox.app_dir + '/Help')
	
	def save_to_stream(self, stream):
		s = self.buffer.get_start_iter()
		e = self.buffer.get_end_iter()
                stream.write(self.buffer.get_text(s, e, TRUE))
	
	def set_uri(self, uri):
                self.uri = uri
		self.buffer.set_modified(FALSE)
                self.update_title()
	
	def new(self):
		EditWindow()
	
	def change_font(self):
		style = self.text.get_style().copy()
		style.font = load_font(options.get('edit_font'))
		self.text.set_style(style)
	
	def show_options(self):
		rox.edit_options()
	
	def set_marked(self, start = None, end = None):
		"Set the marked region (from the selection if no region is given)."
		self.clear_marked()
		assert not self.marked

		buffer = self.buffer
		if start:
			assert end
		else:
			assert not end
			start, end = self.get_selection_range()
		buffer.move_mark(self.mark_start, start)
		buffer.move_mark(self.mark_end, end)
		buffer.apply_tag_by_name('marked',
			buffer.get_iter_at_mark(self.mark_start),
			buffer.get_iter_at_mark(self.mark_end))
		self.marked = 1
	
	def clear_marked(self):
		if not self.marked:
			return
		self.marked = 0
		buffer = self.buffer
		buffer.remove_tag_by_name('marked',
			buffer.get_iter_at_mark(self.mark_start),
			buffer.get_iter_at_mark(self.mark_end))
	
	def undo(self, widget = None):
		self.buffer.undo()
	
	def redo(self, widget = None):
		self.buffer.redo()

	def goto(self, widget = None):
		from goto import Goto
		self.set_minibuffer(Goto())
	
	def search(self, widget = None):
		from search import Search
		self.set_minibuffer(Search())
	
	def process(self, widget = None):
		from process import Process
		self.set_minibuffer(Process())
	
	def set_mini_label(self, label):
		self.mini_label.set_text(label)

	def set_minibuffer(self, minibuffer):
		assert not minibuffer or isinstance(minibuffer, Minibuffer)

		self.minibuffer = minibuffer

		if minibuffer:
			self.mini_entry.set_text('')
			minibuffer.setup(self)
			self.mini_entry.grab_focus()
			self.mini_hbox.show_all()
		else:
			self.mini_hbox.hide()
			self.text.grab_focus()
	
	def mini_key_press(self, entry, kev):
		if kev.keyval == g.keysyms.Escape:
			self.set_minibuffer(None)
			return 1
		if kev.keyval == g.keysyms.Return or kev.keyval == g.keysyms.KP_Enter:
			self.minibuffer.activate()
			return 1

		return self.minibuffer.key_press(kev)
	
	def mini_changed(self, entry):
		self.minibuffer.changed()

	def mini_show_info(self, *unused):
		assert self.minibuffer
		if self.info_box:
			self.info_box.destroy()
		self.info_box = g.MessageDialog(self, 0, g.MESSAGE_INFO, g.BUTTONS_OK,
						self.minibuffer.info)
		self.info_box.set_title('Minibuffer help')
		def destroy(box):
			self.info_box = None
		self.info_box.connect('destroy', destroy)
		self.info_box.show()
		self.info_box.connect('response', lambda w, r: w.destroy())
	
class SelectionSaver(Saveable):
	def __init__(self, window):
		self.window = window
		window.set_marked()
	
	def save_to_stream(self, stream):
		s, e = self.window.get_marked_range()
		stream.write(self.window.buffer.get_text(s, e, TRUE))
	
	def destroy(self):
		# Called when savebox is remove. Get rid of the selection marker
		self.window.clear_marked()
