"""
interface.py - a GTK+ frontend for the Apoo processor
Copyright (C) 2006 Ricardo Cruz <rpmcruz@clix.pt>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""

import os, sys
import ConfigParser
import pygtk, gtk, pango, gobject

# user needs python-gnome-extras for HTML help
HAS_HTML_SUPPORT = True
try: import gtkhtml2
except: HAS_HTML_SUPPORT = False

from vpu import *
from constants import *

VERSION = "2.0.0"

# FIXME: ain't windows compatible (is it?)
APOO_CONFIG_FILE = os.path.join (os.environ.get("HOME"), ".apoo")
DOCS_PATH = "/usr/share/doc/apoo/"
if not os.path.exists (DOCS_PATH):
 	dirname = os.path.dirname (sys.argv[0])
 	if len (dirname): DOCS_PATH = dirname + "/docs/"
 	else:          DOCS_PATH = "docs/"
DOC_APOO="help_apoo"
DOC_TESTER="help_tester"
DOC_ASSEMBLY="help_assembly"
# Configurable (via arguments):
test_mode = False  # --tester

# Configurable (via the preferences dialog):
# (up-case are variables we don't touch, so we can know the defaults.)
REGISTERS_NB = registers_nb = 8
MAX_STEPS    = max_steps    = 1000   # to cut on infinite loops
INPUT_OUTPUT = input_output = 50001  # magic numbers
OUTPUT_ASCII = output_ascii = 50000
OUTPUT_CR    = output_cr    = 50010

USE_NEW_WINDOW = use_new_window = True
SHORTCUTS_STYLE = shortcuts_style = "desktop"  # "desktop" or "emacs"

# Helper stuff
def digits_on (nb):  # returns the digits of a number (eg. 250 => 3)
	if nb < 10: return 1
	return digits_on (nb / 10) + 1

## Our own widgets
# An extension of GtkTextBuffer (with support for eg. undo) for our Editor widget
class EditorBuffer (gtk.TextBuffer):
	def __init__ (self):
		gtk.TextBuffer.__init__ (self)

		self.create_tag ("comment", foreground = "darkgrey", style = pango.STYLE_OBLIQUE)
		self.create_tag ("assign",  foreground = "darkred")  # eg: function:
		self.create_tag ("instruction", weight = pango.WEIGHT_BOLD)  # eg: loadn

		# actions stacks, so we can undo/redo
		self.do_stack = [] # of type [ ('i', 30, "text"), ('d', 20, "other") ]
		self.do_stack_ptr = 0

		self.connect ("changed", self.text_changed)
		self.insert_handler = self.connect_after ("insert-text", self.text_inserted)
		self.delete_handler = self.connect_after ("delete-range", self.text_deleted)

	# reads given file
	def read (self, filename):
		try:
			file = open (filename, 'r')
			self.handler_block (self.insert_handler)
			self.set_text (file.read())
			self.handler_unblock (self.insert_handler)
			file.close()
			# force highlight
			self.apply_highlighting (0, self.get_line_count())
		except IOError: return False
		return True

	# when text changes, disable emacs' mark region
	def text_changed (self, buffer):
		mark = self.get_mark ("emacs-mark")
		if mark: self.delete_mark (mark)

	# as-you-type highlighting. This works by iterate thorugh every line that is touched.
	def apply_highlighting (self, start_line, end_line):
		it = self.get_iter_at_line (start_line)
		self.remove_all_tags (it, self.get_iter_at_line (end_line+1))

		while it.get_line() <= end_line and not it.is_end():
			if not self.skip_blanks (True, it): break
			start_it = it.copy()

			if not self.skip_blanks (False, it):
				end_it = self.get_end_iter()
			else:
				end_it = it.copy()

			word = self.get_text (start_it, end_it, False)

			if word[-1] == ':':
				end_it.backward_char()  # don't highlight the ':'
				self.apply_tag_by_name ("assign", start_it, end_it)
			elif word[0] == '#':
				end_it = start_it.copy()
				end_it.forward_line()
				self.apply_tag_by_name ("comment", start_it, end_it)
			else:
				for categories in inst:
					for i in categories:
						if word == i:
							self.apply_tag_by_name ("instruction", start_it, end_it)
	def is_blank (self, ch):  # helpers
		return ch == ' ' or ch == '\t' or ch == '\n'
	def skip_blanks (self, skip, iter):
		while self.is_blank (iter.get_char()) == skip:
			if not iter.forward_char(): return False
		return True

	# text insertion and deletion event catch is meant to do as-you-type highlighting
	def text_inserted (self, buffer, iter, text, length):
		start = iter.copy()
		start.set_offset (iter.get_offset() - length)
		self.apply_highlighting (start.get_line(), iter.get_line())

		# if user typed return, apply the same identation as the previous line
		if length == 1 and text == "\n":
			start_it = iter.copy()
			if start_it.backward_line():
				end_it = start_it.copy()
				while end_it.get_char() == ' ' or end_it.get_char() == '\t':
					end_it.forward_char()

				if start_it.compare (end_it) != 0:  # there is identation
					ident = buffer.get_text (start_it, end_it)	
					buffer.insert (iter, ident)

		action = ('i', iter.get_offset() - length, text)
		self.do_stack [self.do_stack_ptr:] = [action]
		self.do_stack_ptr += 1

	def text_deleted (self, buffer, start_it, end_it):
		self.apply_highlighting (start_it.get_line(), end_it.get_line())

		action = ('d', start_it.get_offset(), buffer.get_text (start_it, end_it, False))
		self.do_stack [self.do_stack_ptr:] = [action]
		self.do_stack_ptr += 1

	# undo & redo implementation
	def do (self, action, undo):
		iter = self.get_iter_at_offset (action[1])
		if action[0] == 'i' and not undo:
			self.handler_block (self.insert_handler)
			self.insert (iter, action[2])
			self.handler_unblock (self.insert_handler)
		else:
			end_iter = self.get_iter_at_offset (action[1] + len (action[2]))
			self.handler_block (self.delete_handler)
			self.delete (iter, end_iter)
			self.handler_unblock (self.delete_handler)

	def undo (self):
		if self.do_stack_ptr > 0 and self.get_editable():
			self.do_stack_ptr -= 1
			action = self.do_stack [self.do_stack_ptr]
			self.do (action, True)

	def redo (self):
		if self.do_stack_ptr < len (self.do_stack) and self.get_editable():
			action = self.do_stack [self.do_stack_ptr]
			self.do_stack_ptr += 1
			self.do (action, False)

	# for emacs like marking of region (ctrl+space)
	def cursor_moved (self, view, step_size, count, extend_selection):
		mark = self.get_mark ("emacs-mark")
		if mark:
			self.select_range (self.get_iter_at_mark (self.get_insert()),
			                   self.get_iter_at_mark (mark))

# The Editor widget, an extension over gtk.TextView to support eg. line numbering
class Editor (gtk.TextView):
	def __init__ (self, parent):
		buffer = EditorBuffer()
		gtk.TextView.__init__ (self, buffer)

		# Set our buffer class to be used
		#buffer = EditorBuffer()
		#self.set_buffer (buffer)

		self.set_tabs (pango.TabArray (4, False))

		# Let's set a monospace-d font for the editor and numbering
		# We need to explicitely specify font size, so let's get what was the font size
		# (which would be the default)
		font_size = pango.PIXELS (self.get_pango_context().get_font_description().get_size())

		font_desc = pango.FontDescription ("monospace " + str (font_size))
		self.modify_font (font_desc)

		metrics = self.get_pango_context().get_metrics (font_desc, None)
		self.digit_width = pango.PIXELS (metrics.get_approximate_digit_width())
		self.digit_height = pango.PIXELS (metrics.get_ascent()) + pango.PIXELS (metrics.get_descent())

		# Save normal/sensitive base color so we can mess with that
		self.normal_color = self.style.base [gtk.STATE_NORMAL]
		self.insensitive_color = self.style.base [gtk.STATE_INSENSITIVE]

		# used to set a different numbering than line numbering. (dictionary type)
		self.alternative_numbering = None

		# this line will be current line and cannot be changed by the user (if > -1)
		# modify if by using set_fixed_line
		self.fixed_line = -1

		self.breakpoints = []
		self.margin_digits = 99  # to force a margin calculation
		self.text_changed (self.get_buffer())  # sets the numbering margin

		self.do_stack = [] # of type [ ('i', 30, "text"), ('d', 20, "other") ]  # for re/undo
		self.do_stack_ptr = 0

		self.connect ("realize", self.realize)  # we need to post-pone gc construction
		self.connect ("expose-event", self.expose)
		self.connect ("button-press-event", self.button_press_event, parent)
		buffer.connect ("changed", self.text_changed)
		self.connect_after ("move-cursor", buffer.cursor_moved)  # for the emacs mark

	# reads given file
	def read (self, filename):
		return self.get_buffer().read (filename)

	# We don't want to set the editor insensitive to allow users to do selections
	# and that. So we mimic half of it.
	def set_sensitive (self, sensitive):
		self.set_editable (sensitive)
		self.set_cursor_visible (sensitive)
		if sensitive: self.modify_base (gtk.STATE_NORMAL, self.normal_color)
		else:         self.modify_base (gtk.STATE_NORMAL, self.insensitive_color)

	# -1 to disable fixed current line
	def set_fixed_line (self, line):
		self.fixed_line = line
		if line > -1:
			buffer = self.get_buffer()
			it = buffer.get_iter_at_line (line)
			buffer.place_cursor (it)
			return it

	def realize (self, widget):
		# build it here than on every expose for obvious performance reasons
		self.curline_highlight_gc = gtk.gdk.GC (widget.window)
		self.curline_highlight_gc.set_rgb_fg_color (gtk.gdk.color_parse ("lightblue"))
		self.breakline_highlight_gc = gtk.gdk.GC (widget.window)
		self.breakline_highlight_gc.set_rgb_fg_color (gtk.gdk.color_parse ("lightgreen"))

	# expose is the GTK+ draw callback
	def expose (self, widget, event):
		# current line -- allow overrule
		if self.fixed_line >= 0:
			cursor_line = self.fixed_line
		else:
			cursor_line = self.get_buffer().get_iter_at_mark (self.get_buffer().get_insert()).get_line()

		# left window -- draw line numbering
		window = self.get_window (gtk.TEXT_WINDOW_LEFT)
		if event.window == window:
			visible_text = self.get_visible_rect()
			iter = self.get_iter_at_location (visible_text.x, visible_text.y)

			layout = pango.Layout (self.get_pango_context())

			while True:  # a do ... while would be nicer :/
				rect = self.get_iter_location (iter)
				x, y = self.buffer_to_window_coords (gtk.TEXT_WINDOW_LEFT, rect.x, rect.y)

				if y > event.area.y + event.area.height: break
				if y + self.digit_height > event.area.y:
					line = iter.get_line()
					if self.alternative_numbering:
						try:    line = self.alternative_numbering [line]
						except: line = -1
					else:
						line += 1  # start counting from 1, not from 0

					if iter.get_line() in self.breakpoints:
						gc = self.breakline_highlight_gc
						w = self.get_border_window_size (gtk.TEXT_WINDOW_LEFT)
						h = rect.height
						window.draw_arc (gc, True, 0, y, w, h, 64 * 90, 64 * 180)
						window.draw_rectangle (gc, True, w/2, y, w/2, h+1)

					if line >= 0:
						text = str (line).rjust (self.margin_digits, " ")  # align at right

						if iter.get_line() == cursor_line:
							text = "<b>" + text + "</b>"
						if self.alternative_numbering:
							text = "<span foreground=\"blue\">" + text + "</span>"

						layout.set_markup (text)
						self.style.paint_layout (window, gtk.STATE_NORMAL, False, event.area,
						                         widget, "", 2, y, layout)

				if not iter.forward_line():
					break

		# text window -- highlight current line
		window = self.get_window (gtk.TEXT_WINDOW_TEXT)
		if event.window == window:
			"""
			# highlight breaklines too
			for i in self.breakpoints:
				it = self.get_buffer().get_iter_at_line (i)
				rect = self.get_iter_location (it)
				x, y = self.buffer_to_window_coords (gtk.TEXT_WINDOW_TEXT, rect.x, rect.y)
				w = self.allocation.width
				h = rect.height
				gc = self.breakline_highlight_gc
				window.draw_rectangle (gc, True, x, y, w, h)
			"""
			# do the current line highlighting now
			gc = None
			if self.fixed_line == -1:
				gc = widget.style.bg_gc [gtk.STATE_NORMAL]
			else:
				gc = self.curline_highlight_gc

			rect = self.get_iter_location (self.get_buffer().get_iter_at_line (cursor_line))
			x, y = self.buffer_to_window_coords (gtk.TEXT_WINDOW_TEXT, 0, rect.y)

			window.draw_rectangle (gc, True, x, y, widget.allocation.width, rect.height)
		return False

	# button pressed on numbering pane -- move cursor to that line
	# for two clicks, set break point
	# parent is a hook to be able to access Interface
	def button_press_event (self, widget, event, parent):
		if event.window == self.get_window (gtk.TEXT_WINDOW_LEFT):
			x, y = self.window_to_buffer_coords (gtk.TEXT_WINDOW_LEFT, int (event.x), int (event.y))
			it = self.get_iter_at_location (x, y)
			self.get_buffer().place_cursor (it)

			if event.type == gtk.gdk._2BUTTON_PRESS and self.alternative_numbering:
				line = it.get_line()

				# see if there is already one -- if so, remove it
				if line in self.breakpoints:  # remove it
					try:
						i = parent.vpu.lines.index (line+1)
					except ValueError:
						parent.message.write ("%s %s" % ("Cannot clear a break point in line", line+1),
						                      "white")
					else:
						try:
							parent.vpu.clearbreak (i)
						except:
							parent.message.write ("Error: %s, %s" % (sys.exc_type, sys.exc_value), "red")
						else:
							self.breakpoints.remove (line)
				else:  # add break point
					try:
						i = parent.vpu.lines.index (line+1)
					except ValueError:
						parent.message.write ("%s %s" % ("Cannot set a break point in line", line+1), "white")
					else:
						try:
							parent.vpu.setbreak (i)
						except:
							parent.message.write ("Error: %s, %s" % (sys.exc_type, sys.exc_value), "red")
						else:
							self.breakpoints.append (line)

	def reload_breakpoints (self, vpu):
		for i in self.breakpoints:
			pt = vpu.lines.index (i+1)
			vpu.setbreak (pt)

	# we use this so we know when lines are inserted or removed and change the line numbering
	# border accordingly
	def text_changed (self, buffer):
		digits = digits_on (buffer.get_line_count())
		if digits != self.margin_digits:
			self.margin_digits = digits
			margin = (self.digit_width * digits) + 4
			self.set_border_window_size (gtk.TEXT_WINDOW_LEFT, margin)

# A very-visible colored-enabled message label
# NOTE: gtk.Label doesn't have a background and we shouldn't use a gtk.EventBox container because
# gtk-qt-engine doesn't honor its background, so we will use the expose callback
class MessageLabel (gtk.Label):
	def __init__(self):
		gtk.Label.__init__ (self)
		self.set_padding (0, 6)
		self.set_line_wrap (True)
		self.connect ("expose-event", self.expose)

	def write (self, text, bg_color):
		if self.window != None:
			self.set_markup ("<big><b>" + text + "</b></big>")
			self.modify_bg (gtk.STATE_NORMAL, gtk.gdk.color_parse (bg_color))
			# if we want to also have a text_color parameter:
			#self.modify_fg (gtk.STATE_NORMAL, gtk.gdk.color_parse (text_color))

	def expose (self, widget, event):
		self.style.paint_box (widget.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, event.area,
		                      widget, "", widget.allocation.x, widget.allocation.y,
		                      widget.allocation.width, widget.allocation.height)
		return False

# The setup dialog
def read_config():
	config = ConfigParser.ConfigParser ()
	config.read (APOO_CONFIG_FILE)

	global shortcuts_style, use_new_window
	global registers_nb, max_steps, input_output, output_ascii, output_cr

	if config.has_option ("appearance", "keys-shortcuts"):
		shortcuts_style = config.get ("appearance", "keys-shortcuts")
	if config.has_option ("appearance", "use-new-window"):
		use_new_window = config.getboolean ("appearance", "use-new-window")

	if config.has_option ("advanced", "registers-nb"):
		registers_nb = config.getint ("advanced", "registers-nb")
	if config.has_option ("advanced", "max-steps"):
		max_steps = config.getint ("advanced", "max-steps")
	if config.has_option ("advanced", "input-output-mem"):
		input_output = config.getint ("advanced", "input-output-mem")
	if config.has_option ("advanced", "output-ascii-mem"):
		output_ascii = config.getint ("advanced", "output-ascii-mem")
	if config.has_option ("advanced", "output-cr-mem"):
		output_cr = config.getint ("advanced", "output-cr-mem")

def write_config():
	config = ConfigParser.ConfigParser ()

	config.add_section ("appearance")
	config.set ("appearance", "keys-shortcuts", shortcuts_style)
	config.set ("appearance", "use-new-window", use_new_window)

	config.add_section ("advanced")
	config.set ("advanced", "registers-nb", int (registers_nb))
	config.set ("advanced", "max-steps", int (max_steps))
	config.set ("advanced", "input-ouput-mem", int (input_output))
	config.set ("advanced", "output-ascii-mem", int (output_ascii))
	config.set ("advanced", "output-cr-mem", int (output_cr))

	file = open (APOO_CONFIG_FILE, 'w')
	config.write (file)
	file.close()

class Preferences (gtk.Dialog):
	def __init__ (self, parent):
		gtk.Dialog.__init__ (self, "Preferences", parent, gtk.DIALOG_DESTROY_WITH_PARENT,
		                     (gtk.STOCK_REVERT_TO_SAVED, 1, gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
		self.set_default_response (gtk.RESPONSE_CLOSE)
		self.set_has_separator (False)

		look_feel_box = gtk.VBox (False, 6)
		advanced_box = gtk.VBox (False, 6)

		self.desktop_keys = gtk.RadioButton (label = "_Desktop definitions")
		self.emacs_keys = gtk.RadioButton (label = "_Emacs defaults", group = self.desktop_keys)
		# set this data, so we can differ them
		self.desktop_keys.connect ("toggled", self.desktop_keys_cb)
		self.emacs_keys.connect ("toggled", self.emacs_keys_cb)
		keys_box = gtk.VBox (True, 2)
		keys_box.pack_start (self.desktop_keys)
		keys_box.pack_start (self.emacs_keys)
		if shortcuts_style == "emacs":
			self.emacs_keys.set_active (True)
			# the other is the default anyway
		self.new_window = gtk.CheckButton ("Create/open files on _new windows")
		self.new_window.set_active (use_new_window)
		self.new_window.connect ("toggled", self.use_new_window_cb)

		look_feel_box.pack_start (self.create_frame ("Key Shortcuts", keys_box), False)
		look_feel_box.pack_start (self.create_frame ("Miscellaneous", self.new_window), False)

		self.regs_spin = gtk.SpinButton(gtk.Adjustment (registers_nb, 0, 50, 1, 2, 1))
		self.steps_spin = gtk.SpinButton(gtk.Adjustment (max_steps, 10, 65500, 1, 2, 1))
		self.in_out_spin = gtk.SpinButton (gtk.Adjustment (input_output, 0, 65500, 1, 2, 1))
		self.ascii_out_spin = gtk.SpinButton (gtk.Adjustment (output_ascii, 0, 65500, 1, 2, 1))
		self.cr_out_spin = gtk.SpinButton (gtk.Adjustment (output_cr, 0, 65500, 1, 2, 1))

		self.regs_spin.connect ("value-changed", self.regs_changed_cb)
		self.steps_spin.connect ("value-changed", self.steps_changed_cb)
		self.in_out_spin.connect ("value-changed", self.in_out_changed_cb)
		self.ascii_out_spin.connect ("value-changed", self.ascii_out_changed_cb)
		self.cr_out_spin.connect ("value-changed", self.cr_out_changed_cb)

		cpu_grid = self.create_grid ("Machine Processor",
			[ ("_Number of registers", self.regs_spin), ("_Maximum steps", self.steps_spin) ] )
		mem_grid = self.create_grid ("Memory Mapping",
			[ ("_Integer input/output", self.in_out_spin), ("_ASCII output", self.ascii_out_spin),
			  ("_CR output", self.cr_out_spin) ] )

		advanced_box.pack_start (cpu_grid, False)
		advanced_box.pack_start (mem_grid, False)

		notebook = gtk.Notebook()
		notebook.append_page (look_feel_box, gtk.Label ("_Look 'n Feel"))
		notebook.append_page (advanced_box,  gtk.Label ("_Advanced"))
		# we need to set the mnemonic in effect manually
		for i in notebook.get_children():
			label = notebook.get_tab_label (i)
			label.set_use_underline (True)
			label.set_mnemonic_widget (i)

		notebook.set_border_width (6)
		notebook.show_all()
		self.vbox.pack_start (notebook)

		self.connect ("response", self.response)
		self.connect ("close", self.close_dialog)
		self.connect ("delete-event", self.delete_event)  # don't let it

	def close_dialog (self, dialog):
		dialog.hide()
	def delete_event (self, widget, event):
		self.hide()
		return True

	def response (self, dialog, response_id):
		if response_id == gtk.RESPONSE_NONE or response_id == gtk.RESPONSE_CLOSE:
			dialog.hide()
		if response_id == 1:  # revert
			# load defaults
			if SHORTCUTS_STYLE == "emacs":
				self.emacs_keys.set_active (True)
			else:
				self.desktop_keys.set_active (True)
			# the other either is or gets toggled anyway...
			self.new_window.set_active (USE_NEW_WINDOW)

			self.regs_spin.set_value (REGISTERS_NB)
			self.steps_spin.set_value (MAX_STEPS)
			self.in_out_spin.set_value (INPUT_OUTPUT)
			self.ascii_out_spin.set_value (OUTPUT_ASCII)
			self.cr_out_spin.set_value (OUTPUT_CR)

	def regs_changed_cb (self, spin):
		global registers_nb
		registers_nb = spin.get_value()
		return False

	def steps_changed_cb (self, spin):
		global max_steps
		max_steps = spin.get_value()
		return False

	def in_out_changed_cb (self, spin):
		global input_output
		input_output = spin.get_value()
		return False

	def ascii_out_changed_cb (self, spin):
		global output_ascii
		output_ascii = spin.get_value()
		return False

	def cr_out_changed_cb (self, spin):
		global output_cr
		output_cr = spin.get_value()
		return False

	def use_new_window_cb (self, button):
		global use_new_window
		use_new_window = button.get_active()

	def shortcut_keys_changed (self):
		for i in interfaces:
			i.load_menu()

	def desktop_keys_cb (self, button):
		global shortcuts_style
		if button.get_active():
			shortcuts_style = "desktop"
		self.shortcut_keys_changed()

	def emacs_keys_cb (self, button):
		global shortcuts_style
		if button.get_active():
			shortcuts_style = "emacs"
		self.shortcut_keys_changed()

	# to cut down on code size
	def create_frame (self, title, widget):
		frame = gtk.Frame ("<b>" + title + "</b>")
		frame.set_shadow_type (gtk.SHADOW_NONE)
		frame.get_label_widget().set_use_markup (True)
		frame.set_border_width (6)

		# the sole purpose of the box is to set border and padding
		box = gtk.HBox (False, 0)
		box.pack_start (widget, padding = 11)
		box.set_border_width (4)
		frame.add (box)

		return frame

	def create_grid (self, title, entries):
		table = gtk.Table (2, len (entries))
		for i in entries:
			label = gtk.Label (i[0] + ":")
			widget = i[1]
			row = entries.index (i)
			table.attach (label, 0, 1, row, row+1, xoptions = gtk.FILL, yoptions = gtk.FILL)
			table.attach (widget, 1, 2, row, row+1, xoptions = gtk.FILL, yoptions = gtk.FILL)

			label.set_use_underline (True)
			label.set_mnemonic_widget (widget)
			label.set_alignment (0.0, 0.5)

		table.set_col_spacings (6)
		table.set_row_spacings (6)
		return self.create_frame (title, table)

## Our interface
# used as a ref to keep gtk loop alive until there are still windows open, and
# also to apply settings changes to all windows.
interfaces = []

class Interface (gtk.Window):
	def __init__ (self, filename = ""):
		gtk.Window.__init__ (self)
		self.set_default_size (500, 550)
		self.connect ("delete-event", self.ask_close_window_cb)
		self.connect ("destroy", self.close_window_cb)

		# Editor (the text view)
		self.editor = Editor (self)
		editor_window = gtk.ScrolledWindow()
		editor_window.add (self.editor)
		editor_window.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
		editor_window.set_shadow_type (gtk.SHADOW_IN)

		# the editor "status bar"
		self.program_status = ""
		self.editor_status = gtk.Label("")  # editor and program status info
		self.editor_status.set_alignment (1.0, 0.5)
		if not test_mode:
			editor_unlock_button = gtk.Button ("_Edit")  # to unlock the editor
			editor_unlock_button.connect ("clicked", self.unlock_editor_cb)
			editor_unlock_button.set_size_request (80, -1)
		self.set_editor_status ("ready")

		editor_statusbar = gtk.HBox (False, 6)
		editor_statusbar.pack_start (self.editor_status, True)
		if not test_mode:
			editor_statusbar.pack_start (editor_unlock_button, False)

		# GTK >= 2.10 is much nicer to follow cursor movement, so we'll just enable
		# that for it.
# 		if gtk.pygtk_version >= (2,10,0):
# 			self.editor.connect ("notify:cursor-position", self.cursor_moved_cb)

		editor_box = gtk.VBox (False, 0)
		editor_box.pack_start (editor_window, expand = True)
		editor_box.pack_start (editor_statusbar, expand = False)

		# Buttons
		buttons_box = gtk.VBox (True, 0)
		self.load_button = gtk.Button ("_Load")
		self.run_button = gtk.Button ("_Run")
		self.step_button = gtk.Button ("_Step")
		self.continue_button = gtk.Button ("_Continue")
		self.clear_button = gtk.Button ("Cle_ar")

		self.load_button.connect  ("clicked", self.load_program_cb)
		self.run_button.connect   ("clicked", self.run_program_cb)
		self.step_button.connect  ("clicked", self.step_program_cb)
		self.continue_button.connect ("clicked", self.continue_program_cb)
		self.clear_button.connect ("clicked", self.clear_program_cb)

		buttons_box.pack_start (self.load_button)
		buttons_box.pack_start (self.run_button)
		buttons_box.pack_start (self.step_button)
		buttons_box.pack_start (self.continue_button)
		buttons_box.pack_start (self.clear_button)

		# we need this for the menu key shortcuts
		self.accel_group = gtk.AccelGroup()
		self.add_accel_group (self.accel_group)

		# the menu bar
		self.menu = gtk.MenuBar()
		self.load_menu()

		# Informative entries (Program counter & timer)
		self.counter, counter_box = self.create_informative ("_Program Counter")
		self.timer, timer_box = self.create_informative ("_Timer")
		self.counter.modify_text (gtk.STATE_NORMAL, gtk.gdk.Color (0, 0, 65500))

		informative_box = gtk.HBox (False, 12)
		informative_box.pack_start (gtk.Label(), expand = True)
		informative_box.pack_start (counter_box, expand = False)
		informative_box.pack_start (timer_box, expand = False)
		informative_box.pack_start (gtk.Label(), expand = True)

		# Output text
		self.output = gtk.TextView()
		self.output.set_editable (False)
		self.output.set_cursor_visible (False)

		window = gtk.ScrolledWindow()
		window.add (self.output)
		window.set_policy (gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
		window.set_shadow_type (gtk.SHADOW_IN)

		label = gtk.Label ("<span foreground=\"red\"><b>_Output</b></span>")
		label.set_use_markup (True)
		label.set_use_underline (True)
		label.set_mnemonic_widget (self.output)

		output_box = gtk.VBox (False, 4)
		output_box.pack_start (label, expand = False)
		output_box.pack_start (window, expand = True)

		# Registers table
		self.registers, registers_box = self.create_list ("Re_gisters")
		model = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_STRING)
		self.registers.set_model (model)

		self.registers.append_column (gtk.TreeViewColumn ("", gtk.CellRendererText(), text = 0))
		self.registers.append_column (gtk.TreeViewColumn ("", gtk.CellRendererText(), text = 1))
		self.registers.get_column (0).set_expand (True)
		self.registers.get_column (1).set_expand (True)

		# Memory table
		self.memory, memory_box = self.create_list ("_Memory Data")

		model = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_INT, gobject.TYPE_STRING)
		self.memory.set_model (model)

		cell = gtk.CellRendererText()
		cell.set_property ("foreground", "darkred")
		self.memory.append_column (gtk.TreeViewColumn ("Label", cell, text = 0))
		cell = gtk.CellRendererText()
		cell.set_property ("foreground", "blue")
		self.memory.append_column (gtk.TreeViewColumn ("Address", cell, text = 1))
		self.memory.append_column (gtk.TreeViewColumn ("Contents", gtk.CellRendererText(), text = 2))
		self.memory.get_column (0).set_expand (True)
		self.memory.get_column (1).set_expand (True)
		self.memory.get_column (2).set_expand (True)
		self.memory.set_headers_visible (True)

		# Stack list
		self.stack, stack_box = self.create_list ("System _Stack")

		self.stack.append_column (gtk.TreeViewColumn ("", gtk.CellRendererText(), text = 0))
		self.stack.set_model (gtk.ListStore (gobject.TYPE_STRING))

		# Message label
		self.message = MessageLabel()

		# so we can set a border to it
		message_box = gtk.EventBox()
		message_box.add (self.message)
		message_box.set_border_width (6)

		# Layout
		editor_buttons_box = gtk.HBox (False, 12)
		editor_buttons_box.pack_start (editor_box, expand = True)
		editor_buttons_box.pack_start (buttons_box, expand = False)

		lists_box = gtk.HBox (False, 12)
		lists_box.pack_start (output_box, expand = True)
		lists_box.pack_start (registers_box, expand = True)
		lists_box.pack_start (memory_box, expand = True)
		lists_box.pack_start (stack_box, expand = True)
		lists_box.set_size_request (-1, 150)

		informations_box = gtk.VBox (False, 12)
		informations_box.pack_start (informative_box, expand = False)
		informations_box.pack_start (lists_box, expand = True)

		editor_lists_pane = gtk.VPaned()
		editor_lists_pane.pack1 (editor_buttons_box, True, False)
		editor_lists_pane.pack2 (informations_box, False, False)

		# main_box usage is to have a border around the widgets
		main_box = gtk.VBox (False, 0)
		main_box.pack_start (editor_lists_pane, expand = True)
		main_box.pack_start (message_box, expand = False)

		editor_buttons_box.set_border_width (6)
		informations_box.set_border_width (6)

		box = gtk.VBox (False, 0)
		box.pack_start (self.menu, expand = False)
		box.pack_start (main_box, expand = True)

		self.add (box)
		self.show_all()

		self.read_file (filename)

		global interfaces
		interfaces.append (self)

		self.preferences = Preferences (self)

	def load_menu (self):
		# remove any current entries
		for i in self.menu.get_children():
			self.menu.remove (i)

		file_menu = gtk.Menu()
		item = self.add_menu_item (self.menu, "_File")
		item.set_submenu (file_menu)
		if not test_mode:
			self.add_menu_item (file_menu, "_New", gtk.STOCK_NEW, self.file_new_cb)
			self.add_menu_item (file_menu, "_Open", gtk.STOCK_OPEN, self.file_open_cb)
			self.add_menu_item (file_menu, "-")
			self.add_menu_item (file_menu, "_Save", gtk.STOCK_SAVE, self.file_save_cb)
			self.add_menu_item (file_menu, "Save _As", gtk.STOCK_SAVE_AS, self.file_save_as_cb)
			self.add_menu_item (file_menu, "-")
		self.add_menu_item (file_menu, "_Close", gtk.STOCK_CLOSE, self.file_close_cb)
		self.add_menu_item (file_menu, "_Quit", gtk.STOCK_QUIT, self.file_quit_cb)

		edit_menu = gtk.Menu()
		item = self.add_menu_item (self.menu, "Edi_t")
		item.set_submenu (edit_menu)
		if not test_mode:
			self.add_menu_item (edit_menu, "_Undo", gtk.STOCK_UNDO, self.edit_undo_cb,
			                    [ (gtk.gdk.CONTROL_MASK, ord ('z')) ])
			self.add_menu_item (edit_menu, "_Redo", gtk.STOCK_REDO, self.edit_redo_cb,
			                    [ (gtk.gdk.CONTROL_MASK|gtk.gdk.SHIFT_MASK, ord ('z')) ])
			self.add_menu_item (edit_menu, "-")

			if shortcuts_style == "emacs":
				self.add_menu_item (edit_menu, "Kill Line", None, self.edit_kill_line_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('k')) ])
				self.add_menu_item (edit_menu, "Yank", None, self.edit_yank_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('y')) ])
				self.add_menu_item (edit_menu, "-")
				self.add_menu_item (edit_menu, "Mark Region", None, self.edit_mark_region_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord (' ')) ])
				# GTK+ doesn't support an accelerator like Esc+W
				self.add_menu_item (edit_menu, "Copy Region as Kill", None,
				                    self.edit_copy_region_as_kill_cb,
				                    [ (gtk.gdk.MOD1_MASK, 65307), (gtk.gdk.MOD1_MASK, ord ('w'))] )
				self.add_menu_item (edit_menu, "Kill Region", None, self.edit_kill_region_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('w')) ])
				self.add_menu_item (edit_menu, "-")
				self.add_menu_item (edit_menu, "Line Home", None, self.edit_line_home_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('a')) ])
				self.add_menu_item (edit_menu, "Line End", None, self.edit_line_end_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('e')) ])
				self.add_menu_item (edit_menu, "Buffer Home", None, self.edit_buffer_home_cb,
				                    [ (gtk.gdk.CONTROL_MASK, 65360) ])
				self.add_menu_item (edit_menu, "Buffer End", None, self.edit_buffer_end_cb,
				                    [ (gtk.gdk.CONTROL_MASK, 65367) ])
				self.add_menu_item (edit_menu, "-")
				self.add_menu_item (edit_menu, "Delete Right Character", None,
				                    self.edit_delete_right_char_cb,
				                    [ (gtk.gdk.CONTROL_MASK, ord ('d')) ])
			else:  # "desktop"
				self.add_menu_item (edit_menu, "Cu_t", gtk.STOCK_CUT, self.edit_cut_cb)
				self.add_menu_item (edit_menu, "_Copy", gtk.STOCK_COPY, self.edit_copy_cb)
				self.add_menu_item (edit_menu, "_Paste", gtk.STOCK_PASTE, self.edit_paste_cb)
			self.add_menu_item (edit_menu, "-")
		self.add_menu_item (edit_menu, "_Preferences...", gtk.STOCK_PREFERENCES,
		                    self.edit_preferences_cb)

		help_menu = gtk.Menu()
		item = self.add_menu_item (self.menu, "_Help")
		item.set_submenu (help_menu)
		self.add_menu_item (help_menu, "Apoo _Interface Help...", gtk.STOCK_HELP, self.help_interface_cb, None)
		self.add_menu_item (help_menu, "Assemply _Language Help...", gtk.STOCK_HELP, self.help_language_cb, None)
		self.add_menu_item (help_menu, "_About...", gtk.STOCK_ABOUT, self.help_about_cb)

		self.menu.show_all()

	# convience methods to create the menu to cut down on code
	def add_menu_item (self, parent, label, image = None, callback = None,
	                   shortcut = []):  # shortcut is a list of type { (modifier, key) ]
		if label == '-':
			item = gtk.SeparatorMenuItem()
		else:
			# we'll create the item widget ourselves since pygtk isn't very nice here
			box = gtk.HBox (False, 6)
			glabel = gtk.AccelLabel (label)
			glabel.set_use_underline (True)
			glabel.set_alignment (0, 0.5)
			if image:
				gimage = gtk.Image()
				gimage.set_from_stock (image, gtk.ICON_SIZE_MENU)
				box.pack_start (gimage, False)
			box.pack_start (glabel, True)

			item = gtk.MenuItem()
			item.add (box)
			glabel.set_accel_widget (item)

			if shortcut:
				if len (shortcut) == 0 and image:
					# set stock image assigned key
					info = gtk.stock_lookup (image)
					shortcut = [ (info[2], info[3]) ]
				for i in shortcut:
					modifier = i[0]
					key = i[1]
					item.add_accelerator ("activate", self.accel_group, key,
					                      modifier, gtk.ACCEL_VISIBLE)
			if callback:
				item.connect ("activate", callback)

		parent.append (item)
		item.show_all()
		return item

	# function to cut down code size
	def create_list (self, title):
		list = gtk.TreeView()
		list.set_headers_visible (False)
		list.get_selection().set_mode (gtk.SELECTION_NONE)
		if gtk.pygtk_version >= (2,10,0):
			list.set_grid_lines (True)
		else:
			list.set_rules_hint (True)

		window = gtk.ScrolledWindow()
		window.add (list)
		window.set_policy (gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
		window.set_shadow_type (gtk.SHADOW_IN)

		label = gtk.Label ("<span foreground=\"red\"><b>" + title + "</b></span>")
		label.set_use_markup (True)
		label.set_use_underline (True)
		label.set_mnemonic_widget (list)

		box = gtk.VBox (False, 4)
		box.pack_start (label, expand = False)
		box.pack_start (window, expand = True)
		return (list, box)

	# another function to cut down code size
	def create_informative (self, title):
		entry = gtk.Entry()
		entry.set_editable (False)  # let's make people see the entry is un-editable...
		entry.modify_base (gtk.STATE_NORMAL, entry.style.base [gtk.STATE_INSENSITIVE]);
		entry.set_text ("0")
		entry.set_size_request (40, -1)

		label = gtk.Label ("<span foreground=\"red\"><b>" + title + ":</b></span>")
		label.set_use_markup (True)
		label.set_use_underline (True)
		label.set_mnemonic_widget (entry)

		box = gtk.HBox (False, 4)
		box.pack_start (label, expand = False)
		box.pack_start (entry, expand = False)
		return (entry, box)

	def lock_editor (self):
		self.editor.set_sensitive (False)

		self.run_button.set_sensitive (True)
		self.step_button.set_sensitive (True)
		self.continue_button.set_sensitive (True)
		self.clear_button.set_sensitive (True)

		buffer = self.output.get_buffer()
		buffer.delete (buffer.get_start_iter(), buffer.get_end_iter())

	def unlock_editor (self):
		self.set_editor_status ("editing")
		self.editor.set_sensitive (True)

		self.editor.breakpoints = []
		self.editor.alternative_numbering = None
		self.editor.set_fixed_line (-1)  # disable fixed line

		self.editor.grab_focus()
		self.run_button.set_sensitive (False)
		self.step_button.set_sensitive (False)
		self.continue_button.set_sensitive (False)
		self.clear_button.set_sensitive (False)

		self.message.write ("", "white")

	def unlock_editor_cb (self, button): self.unlock_editor()

	def finished_program (self):
		self.set_editor_status ("end of program")

	## Apoo bridge
	# cuts text into a list of instructions, like [(0, ["rtn", "R2"]), ...]
	def prepare_program (self):
		text_iter = self.editor.get_buffer().get_start_iter()
		program = []
		linum = 0
		while not text_iter.is_end():
			# gets a text line
			t_iter = text_iter.copy()
			text_iter.forward_line()
			line = t_iter.get_text (text_iter).encode()

			linep = []
			linum = linum + 1
			if len(line) == 1: continue
			line = string.split(line)
			if line:
				if line[0][len(line[0])-1] == ':' and line[0][0] != '#':
					linep.append (line [0][:len (line[0])-1])
				elif line[0][0] == '#':
					continue
				else:
					linep.append ([])
					linep.append (line[0])
				for i in xrange (1, len(line)):
					if line[i][0] == '#': continue
					else: linep.append (line[i])
				program.append ((linum,linep))

		return program

	# syncs cpu data from the apoo engine (registers, memory, stack, etc)
	def load_vpu_status (self):
		# registers
		model = self.registers.get_model()
		model.clear()
		for i in range (len (self.vpu.reg)):
			model.append ( [ "R%d" % i, str(self.vpu.reg[i])])

		# memory data
		model = self.memory.get_model()
		model.clear()
		for i in xrange (len (self.vpu.RAM)):
			it = model.append ( [ '', i, str(self.vpu.RAM[i])] )
			
		# re-iterate to set labels now
		for x in self.vpu.labelm.keys():
			it = model.get_iter (self.vpu.labelm [x])
			model.set (it, 0, "%s" % x)

		# stack
		model = self.stack.get_model()
		model.clear()
		for i in self.vpu.stack:
			it = model.append ([str (i)])

		# timer and program counter
		self.timer.set_text (str (self.vpu.time))
		self.counter.set_text (str (self.vpu.PC))

		# set current line to the VPU one
		try:
			line = self.vpu.lines [self.vpu.PC]
		except IndexError:
			self.message.write ("Out of Program", "red")
		else:
			it = self.editor.set_fixed_line (line - 1)
			# ensure line is visible
			self.editor.scroll_to_iter (it, 0.10)
		self.editor.queue_draw()  # update line highlight

	def load_program_cb (self, button):
		self.set_editor_status ("loading")
		self.lock_editor()
		#self.editor.breakpoints = []

		# boots vpu
		self.vpu = Vpu (registers_nb,
		                { output_ascii:("val = 0", "self.Inter.output_inst (val, True)"),
		                  input_output:("val = self.Inter.input_inst()",
		                                "self.Inter.output_inst (val)"),
		                  output_cr:("val = 0","self.Inter.output_inst()") }, self)

		# load program to VPU
		buffer = self.editor.get_buffer()
		program = self.prepare_program ()
		try:
			self.vpu.load (program)
		except vpuLoadError,error:
			self.message.write ("Parsing Error (line %d): %s" % (error.line, error.message), "red")
			self.set_editor_status ("parsing error")
			self.editor.set_fixed_line (error.line - 1)
		except:
			self.message.write ('Parsing Error: %s, %s' % (sys.exc_type, sys.exc_value), "red")
			self.set_editor_status ("parsing error")
		else:
			self.message.write ("Program Loaded", "white")
			self.set_editor_status ("loaded")

			# set adress numbering
			self.editor.alternative_numbering = {}
			for i in range (len (self.vpu.lines)):
				self.editor.alternative_numbering [self.vpu.lines[i]-1] = i

			self.load_vpu_status()
			self.editor.reload_breakpoints (self.vpu)

	def run_program_cb (self, button):
		try:
			self.vpu.run()
		except OutOfMemory, error:
			self.message.write ("%s: memory address %s not reserved" % (error.message, error.add),
			                    error.colour)
		except vpuError, error:
			self.message.write (error.message, error.colour)
		except:
			self.message.write ('Error: %s, %s' % (sys.exc_type, sys.exc_value), "red")
		else:
			self.message.write ("End of Program", "green")

		self.finished_program()
		self.load_vpu_status()

	def step_program_cb (self, button):
		self.message.write ("Next Step", "white")
		self.set_editor_status ("running")
		try:
			self.vpu.step()
		except OutOfMemory, error:
			self.message.write ("%s: memory address %s not reserved" % (error.message, error.add),
			                    error.colour)
			self.finished_program()
		except vpuError, error:
			self.message.write (error.message, error.colour)
			self.finished_program()
		except:
			self.message.write ("Error: %s, %s" %(sys.exc_type, sys.exc_value), "red")
			self.finished_program()

		self.load_vpu_status()

	def is_on_breakpoint (self):
		try:
			line = self.vpu.lines [self.vpu.PC]
			try:
				i = self.vpu.lines.index (line)
			except ValueError:
				return False
			try:
				b = self.vpu.BreakP.index (i)
			except ValueError:
				return False
			return True
		except IndexError:
			return False

	def continue_program_cb (self, button):
		self.message.write ("Continue Program", "white")
		try:
			self.vpu.cont (max_steps)
		except OutOfMemory, error:
			self.message.write ("%s: memory address %s not reserved" % (error.message, error.add),
			                    error.colour)
			self.finished_program()
		except vpuError, error:
			self.message.write (error.message, error.colour)
			self.finished_program()
		except:
			self.message.write ("Error: %s, %s" %(sys.exc_type, sys.exc_value), "red")
			self.finished_program()

		if self.is_on_breakpoint():
			self.set_editor_status ("at break point")
		else:
			self.finished_program()

		self.load_vpu_status()

	def clear_program_cb (self, button):
		self.editor.breakpoints = []
		self.editor.queue_draw()
		self.vpu.BreakP = []
		self.message.write ("All break points removed", "white")

	def set_window_title (self, filename):
		title = filename + " - Apoo"
		if test_mode: title += " Tester"
		else:         title += " Workbench"
		self.set_title (title)

	def read_file (self, filename):
		if test_mode: self.lock_editor()
		else:         self.unlock_editor()

		buffer = self.editor.get_buffer()
		if filename != "":
			if self.editor.read (filename):
				self.set_window_title (os.path.basename (filename))
			else:			
				print "Error: couldn't open file for reading: ", filename
				filename = ""
				if test_mode: sys.exit (2)

		if filename == "":
			self.set_window_title ("Unnamed")
			buffer.delete (buffer.get_start_iter(), buffer.get_end_iter())

		self.filename = filename
		buffer.set_modified (False)

	# Graphical-dependent instructions
	def output_inst (self, value = '\n', convert_ascii = False):
		buffer = self.output.get_buffer()
		if convert_ascii:
			value = "%c" % (value % 256)
		buffer.insert (buffer.get_end_iter(), "%s" % value)

	def input_inst (self):
		dialog = gtk.Dialog ("Insert Input", self,
		                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
		                     (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
		dialog.set_has_separator (False)
		dialog.set_default_response (gtk.RESPONSE_ACCEPT)

		label = gtk.Label ("Input:")
		entry = gtk.SpinButton (gtk.Adjustment (0, -65500, 65500, 1, 2, 1))
		entry.set_activates_default (True)

		box = gtk.HBox (False, 6)
		box.set_border_width (6)
		box.pack_start (label, expand = False)
		box.pack_start (entry, expand = True)
		box.show_all()
		dialog.vbox.pack_start (box)

		while dialog.run() != gtk.RESPONSE_ACCEPT:
			pass  # force user to accept

		value = entry.get_value()
		dialog.destroy()
		return int (value)

	## event callbacks
	def file_new_cb (self, item):
		if use_new_window:
			new_window = Interface()
		else:
			if not self.ask_close_window_cb (self, None):
				self.read_file ("")
				self.unlock_editor()

	def file_open_cb (self, item):
		if not use_new_window and self.editor.get_buffer().get_modified():
			if self.ask_close_window_cb (self, None):
				return

		dialog = gtk.FileChooserDialog ("Open File", self, gtk.FILE_CHOOSER_ACTION_OPEN,
			buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
		if dialog.run() == gtk.RESPONSE_ACCEPT:
			filename = dialog.get_filename()
			if use_new_window:
				# open file on new window -- unless we just started apoo
				if self.filename == "" and (not self.editor.get_buffer().get_modified()):
					self.read_file (filename)
				else:
					new_window = Interface (filename)
			else:
				self.read_file (filename)
		dialog.destroy()

	def file_save_cb (self, item):
		if self.filename == "":
			return self.file_save_as_cb (item)
		else:
			buffer = self.editor.get_buffer()
			file = open (self.filename, 'w')
			file.write (buffer.get_text (buffer.get_start_iter(), buffer.get_end_iter(), False))
			file.close()
			self.editor.get_buffer().set_modified (False)
			return True

	def file_save_as_cb (self, item):
		dialog = gtk.FileChooserDialog ("Save File", self, gtk.FILE_CHOOSER_ACTION_SAVE,
			buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
		ret = False
		if dialog.run() == gtk.RESPONSE_ACCEPT:
			self.filename = dialog.get_filename()
			if not self.filename.endswith(".apoo"):
				self.filename += ".apoo"
			ret = self.file_save_cb (item)
			if ret:
				self.set_window_title (os.path.basename (self.filename))
		dialog.destroy()
		return ret

	def file_close_cb (self, item):
		if not self.ask_close_window_cb (self, None):
			self.destroy()

	def file_quit_cb (self, item):
		# we can't use a for here cause the interfaces list gets modified on window.destroy
		while len (interfaces):
			i = interfaces[0]
			if not i.ask_close_window_cb (i, None):
				i.destroy()
			else: break

	def edit_undo_cb (self, item):
		self.editor.get_buffer().undo()
	def edit_redo_cb (self, item):
		self.editor.get_buffer().redo()

	def edit_cut_cb (self, item):
		buffer = self.editor.get_buffer()
		clipboard = gtk.clipboard_get()
		buffer.cut_clipboard (clipboard, self.editor.get_editable())

	def edit_copy_cb (self, item):
		buffer = self.editor.get_buffer()
		clipboard = gtk.clipboard_get()
		buffer.copy_clipboard (clipboard)

	def edit_paste_cb (self, item):
		buffer = self.editor.get_buffer()
		clipboard = gtk.clipboard_get()
		buffer.paste_clipboard (clipboard, None, self.editor.get_editable())

	def edit_kill_line_cb (self, item):
		buffer = self.editor.get_buffer()
		start_it = buffer.get_iter_at_mark (buffer.get_insert())
		end_it = start_it.copy()
		end_it.forward_to_line_end()

		clipboard = gtk.clipboard_get()
		clipboard.set_text (buffer.get_text (start_it, end_it, False))
		buffer.delete_interactive (start_it, end_it)

	def edit_yank_cb (self, item):
		buffer = self.editor.get_buffer()
		self.edit_paste_cb (item)

	def edit_mark_region_cb (self, item):
		buffer = self.editor.get_buffer()
		it = buffer.get_iter_at_mark (buffer.get_insert())
		mark = buffer.get_mark ("emacs-mark")
		if mark:
			buffer.move_mark (mark, it)
		else:
			buffer.create_mark ("emacs-mark", it, True)

	def edit_kill_region_cb (self, item):
		buffer = self.editor.get_buffer()
		buffer.delete_selection (True, self.get_editable())

	def edit_copy_region_as_kill_cb (self, item):
		self.edit_cut_cb (item)

	def edit_line_home_cb (self, item):
		buffer = self.editor.get_buffer()
		it = buffer.get_iter_at_mark (buffer.get_insert())
		it.set_line_offset(0)
		buffer.place_cursor (it)

	def edit_line_end_cb (self, item):
		buffer = self.editor.get_buffer()
		it = buffer.get_iter_at_mark (buffer.get_insert())
		it.forward_to_line_end()
		buffer.place_cursor (it)

	def edit_buffer_home_cb (self, item):
		buffer = self.editor.get_buffer()
		it = buffer.get_start_iter()
		buffer.place_cursor (it)

	def edit_buffer_end_cb (self, item):
		buffer = self.editor.get_buffer()
		it = buffer.get_end_iter()
		buffer.place_cursor (it)

	def edit_delete_right_char_cb (self, item):
		buffer = self.editor.get_buffer()
		start_it = buffer.get_iter_at_mark (buffer.get_insert())
		end_it = start_it.copy()
		if end_it.forward_char():
			buffer.delete_interactive (start_it, end_it)

	def edit_preferences_cb (self, item):
		self.preferences.show()

	def show_file_text_dialog (self, title, path,filename):
		dialog = gtk.Dialog (title, self,
		                     buttons = (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
		dialog.set_default_response (gtk.RESPONSE_CLOSE)
		dialog.connect ("response", self.close_file_text_dialog_cb)
		dialog.set_default_size (-1, 450)

		if HAS_HTML_SUPPORT and os.path.isfile(path+"html/"+filename + ".html"):
			document = gtkhtml2.Document()
			document.clear()
			file = open (path+"html/" + filename + ".html", 'r')
			document.open_stream ('text/html')
			document.write_stream (file.read())
			document.close_stream()
			file.close()

			view = gtkhtml2.View()
			view.set_document (document)
			view.set_size_request (400, -1)
		else:  # ascii
			buffer = gtk.TextBuffer()
			file = open (path+filename + ".txt", 'r')
			buffer.set_text (file.read())
			file.close()

			# make fonts monospace
			tag = buffer.create_tag (None, family = "monospace")
			buffer.apply_tag (tag, buffer.get_start_iter(), buffer.get_end_iter())

			view = gtk.TextView (buffer)
			view.set_editable (False)
			view.set_cursor_visible (False)

		window = gtk.ScrolledWindow()
		window.set_policy (gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
		window.set_shadow_type (gtk.SHADOW_IN)
		window.add (view)

		dialog.vbox.pack_start (window, True)
		dialog.show_all()  # not run() because we don't want it to block

	def close_file_text_dialog_cb (self, dialog, response):
		dialog.destroy()

	def help_interface_cb (self, item):
		if test_mode: doc = DOC_TESTER
		else:         doc = DOC_APOO
		self.show_file_text_dialog ("Help on the Apoo Interface",
					    DOCS_PATH,doc)

	def help_language_cb (self, item):
		self.show_file_text_dialog("Help on the Apoo Assembly Language",
					   DOCS_PATH,DOC_ASSEMBLY)

	def help_about_cb (self, item):
		dialog = gtk.AboutDialog()
		dialog.set_name ("Apoo")
		dialog.set_version (VERSION)
		dialog.set_copyright("Licensed under the GNU General Public License")
		
		file = open ("/usr/share/common-licenses/GPL","r")
		dialog.set_license (file.read())
		file.close()
		
		dialog.set_website ("http://www.ncc.up.pt/apoo")
		dialog.set_website_label ("Website")
		dialog.set_authors (["Rogerio Reis <rvr@ncc.up.pt>", "Nelma Moreira <nam@ncc.up.pt>",
		                     "(Apoo main developers)", "",
		                     "Ricardo Cruz <c0607045@alunos.dcc.fc.up.pt>", "(interface developer)"])
		dialog.show()

	def ask_close_window_cb (self, window, event):
		if self.editor.get_buffer().get_modified():
			dialog = gtk.MessageDialog (window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION,
			         gtk.BUTTONS_YES_NO, "Document not saved. Discard it?")
			response = dialog.run()
			dialog.destroy()
			if response != gtk.RESPONSE_YES:
				return True
		return False

	def close_window_cb (self, window):
		global interfaces
		interfaces.remove (self)
		if len (interfaces) == 0:
			gtk.main_quit()
		return False

	def update_editor_statusbar (self, buffer):
		# some trickery to get the cursor position (this is easier for GTK+ >= 2.10.0)
		iter = buffer.get_iter_at_mark (buffer.get_insert())
		col = iter.get_line_offset() + 1
		lin = iter.get_line() + 1
		self.editor_statusbar.push (0, "\tLine: " + str (lin) + " Col: " + str (col))

	def set_editor_status (self, prog_status):
		if prog_status: self.program_status = prog_status

		if gtk.pygtk_version >= (2,10,0):
			buffer = self.editor.get_buffer()
			iter = buffer.get_iter_at_mark (buffer.get_insert())
			col = iter.get_line_offset() + 1
			lin = iter.get_line() + 1

			self.editor_status.set_text ("line: " + str (lin) + " col: " + str (col) +
			                             "\tStatus: " + self.program_status)
		else:
			self.editor_status.set_text ("status: " + self.program_status)

	def cursor_moved_cb (self, position):
		self.set_editor_status (None)

if __name__ == "__main__":
	# parse arguments at first
	filename = ""
	argv = sys.argv
	for i in range (len (argv)):
		if i == 0:
			continue  # ignore invocation name

		if argv[i] == "--tester" or argv[i] == "-t":
			test_mode = True

		elif argv[i] == "--help" or argv[i] == "-h":
			print "Usage: " + argv[0] + " [OPTIONS] [FILENAME]"
			print "Options may be:"
			print "\t--tester, -t\tExecute-only mode"
			print "\t--help, -h\tShow this help text"
			print ""
			sys.exit (0)

		elif argv[i][0] == '-':
			print "Unrecognized argument: " + argv[i]
			print "For usage: " + argv[0] + " --help"
			sys.exit (1)
		else:
			filename = argv[i]

	if test_mode and not len (filename):
		print "Usage: " + argv[0] + " --tester filename"
		sys.exit (1)

	# go on, now
	read_config()

	interface = Interface (filename)
	gtk.main()

	write_config()
