#!/usr/bin/env python
#
# Clarence - programmer's calculator
#
# Copyright (C) 2002 Tomasz Mka <p@ll.pl>
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

import os, sys, string
import gtk, GDK

from math import *


version = "0.1.10"

config = gui = {}
labels = ("DEC   =>", "HEX   =>", "OCT   =>", "ASCII =>", "BIN   =>")
entries = ("entry_dec", "entry_hex", "entry_oct", "entry_asc", "entry_bin")

flist = (
		'bin ("10100010110")', "Return value of binary string.",
		'asc ("something")', "Return value for last four ASCII chars.",
		"ans ()", "Return last result.",
		"or", "Boolean OR",
		"and", "Boolean AND",
		"not x", "Boolean NOT",
		"|", "Bitwise OR",
		"^", "Bitwise XOR",
		"&", "Bitwise AND",
		"~x", "Bitwise NOT",
		"<<, >>", "Shifts",
		"+", "Addition",
		"-", "Subtraction",
		"*", "Multiplication",
		"/", "Division",
		"%", "Remainder",
		"**", "Exponentiation",
		"+x, -x", "Positive, negative",
		"acos (x)", "Return the arc cosine of x.",
		"asin (x)", "Return the arc sine of x.",
		"atan (x)", "Return the arc tangent of x.",
		"atan2 (y, x)", "Return atan(y / x).",
		"ceil (x)", "Return the ceiling of x as a real.",
		"cos (x)", "Return the cosine of x.",
		"cosh (x)", "Return the hyperbolic cosine of x.",
		"exp (x)", "Return e**x.",
		"fabs (x)", "Return the absolute value of the real x.",
		"floor (x)", "Return the floor of x as a real.",
		"fmod (x, y)", "Return x % y.",
		"hypot (x, y)", "Return the Euclidean distance, sqrt(x*x + y*y).",
		"ldexp (x, i)", "Return x * (2**i).",
		"log (x)", "Return the natural logarithm of x.",
		"log10 (x)", "Return the base-10 logarithm of x.",
		"pow (x, y)", "Return x**y.",
		"sin (x)", "Return the sine of x.",
		"sinh (x)", "Return the hyperbolic sine of x.",
		"sqrt (x)", "Return the square root of x.",
		"tan (x)", "Return the tangent of x.",
		"tanh (x)", "Return the hyperbolic tangent of x.",
		"veclen (x0, y0, x1, y1)", "Return the vector's length."
		)

#------------------------------------------------------------

def window_pos_mode(widget):
	if config["window_placement"] == 0:
		widget.set_position(gtk.WIN_POS_NONE)
	elif config["window_placement"] == 1:
		widget.set_position(gtk.WIN_POS_CENTER)
	elif config["window_placement"] == 2:
		widget.set_position(gtk.WIN_POS_MOUSE)

def main_menu(action, widget):
	if action == 1:
		gui["main_entry"].set_text("")
	elif action == 2:
		gtk.mainquit()

def insert_menu(action, widget):
	if action == 1:
		gui["main_entry"].append_text('bin("")')
		gui["main_entry"].set_position(len(gui["main_entry"].get_text())-2)
	elif action == 2:
		gui["main_entry"].append_text('asc("")')
		gui["main_entry"].set_position(len(gui["main_entry"].get_text())-2)
	elif action == 3:
		gui["main_entry"].append_text('ans()')

def select_menu(action, widget):
	if action < 6:
		gui[entries[action-1]].select_region(0, len(gui[entries[action-1]].get_text()))
	else:
		for i in range(5):
			gui[entries[i]].select_region(0, 0)
		gui["main_entry"].grab_focus()

def functions_menu(action, widget):
	if action == 1:
		gui["main_entry"].append_text('veclen(,,,)')
		gui["main_entry"].set_position(len(gui["main_entry"].get_text())-4)

def help_menu(action, widget):
	if action == 1:
		about_window()

#------------------------------------------------------------

def about_window_close(*args):
	gui["about_window"].hide()
	gui["main_window"].set_sensitive(gtk.TRUE)
	gui["main_entry"].grab_focus()

def about_window():
	win = gtk.GtkWindow()
	gui["about_window"]=win
	win.set_policy(gtk.TRUE, gtk.TRUE, gtk.FALSE)
	win.set_title("About")
	win.connect("delete_event", about_window_close)
	win.set_border_width(2)

	window_pos_mode(win)

	frame = gtk.GtkFrame()
	frame.show()
	frame.set_border_width(2)
	win.add(frame)

	vbox = gtk.GtkVBox(spacing=5)
	vbox.show()
	frame.add(vbox)

	label = gtk.GtkLabel("\n\n" + "Clarence (programmer's calculator)\n"
						"\nversion " + version +"\n\n"
						"Written by Tomasz Maka <p@ll.pl>\n")
	set_font(label)
	label.show()
	vbox.pack_start(label)

	entry = gtk.GtkEntry()
	gui["http_entry"] = entry
	entry.set_editable(gtk.FALSE)
	entry.set_usize(290, -2)
	set_font(entry)
	entry.show()
	vbox.pack_start(entry)

	entry.set_text("http://nrn.ll.pl/pasp/clarence")

	button = gtk.GtkButton("OK")
	button.connect("clicked", about_window_close)
	vbox.pack_start(button, expand=gtk.FALSE)
	button.set_flags(gtk.CAN_DEFAULT)
	button.grab_default()
	button.show()

	gui["main_window"].set_sensitive(gtk.FALSE)
	gui["about_window"].show()


#------------------------------------------------------------

def fhelp_window_close(*args):
	gui["fhelp_window"].hide()
	gui["main_window"].set_sensitive(gtk.TRUE)
	gui["main_entry"].grab_focus()

def functions_help_window(*args):
	win = gtk.GtkWindow()
	gui["fhelp_window"]=win
	win.set_policy(gtk.TRUE, gtk.TRUE, gtk.FALSE)
	win.set_title("Available functions")
	win.set_usize(520, 350)
	win.connect("delete_event", fhelp_window_close)
	win.set_border_width(4)

	window_pos_mode(win)

	vbox = gtk.GtkVBox(spacing=5)
	win.add(vbox)
	vbox.show()

	scrolled_window = gtk.GtkScrolledWindow()
	scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
	vbox.pack_start(scrolled_window, expand=gtk.TRUE)
	scrolled_window.show()

	mlist = map(lambda i: "", range(2))

	clist = gtk.GtkCList(2)
	scrolled_window.add_with_viewport(clist)
	set_font(clist)
	clist.set_column_width(0, 180)
	clist.show()

	clist.freeze()
	for i in range(len(flist)/2):
		mlist[0]=flist[i*2]
		mlist[1]=flist[i*2+1]
		clist.append(mlist)
	clist.thaw()

	button = gtk.GtkButton("Close")
	button.connect("clicked", fhelp_window_close)
	vbox.pack_start(button, expand=gtk.FALSE)
	button.set_flags(gtk.CAN_DEFAULT)
	button.grab_default()
	button.show()

	gui["main_window"].set_sensitive(gtk.FALSE)
	gui["fhelp_window"].show()

#------------------------------------------------------------

def warning_window_close(*args):
	gui["warning_window"].hide()
	gui["main_window"].set_sensitive(gtk.TRUE)
	gui["main_entry"].grab_focus()

def warning_window(title, message):
	win = gtk.GtkWindow()
	gui["warning_window"]=win
	win.set_policy(gtk.TRUE, gtk.TRUE, gtk.FALSE)
	win.set_title(title)
	win.set_usize(300, -2)
	win.connect("delete_event", warning_window_close)
	win.set_border_width(4)

	window_pos_mode(win)

	vbox = gtk.GtkVBox(spacing=5)
	win.add(vbox)
	vbox.show()

	label = gtk.GtkLabel("\n\n" + message + "\n\n")
	set_font(label)
	label.show()
	vbox.pack_start(label)

	button = gtk.GtkButton("Close")
	button.connect("clicked", warning_window_close)
	vbox.pack_start(button, expand=gtk.FALSE)
	button.set_flags(gtk.CAN_DEFAULT)
	button.grab_default()
	button.show()

	gui["main_window"].set_sensitive(gtk.FALSE)
	gui["warning_window"].show()

#------------------------------------------------------------

def create_main_window(*args):
	win = gtk.GtkWindow()
	gui["main_window"]=win
	win.set_policy(gtk.TRUE, gtk.TRUE, gtk.FALSE)
	win.set_title("Clarence v" + version)
	win.set_usize(config["win_width"], config["win_height"])
#	win.set_uposition(config["win_pos_x"], config["win_pos_y"])
	win.connect("delete_event", gtk.mainquit)

	window_pos_mode(win)

	vbox1 = gtk.GtkVBox(spacing=5)
	win.add(vbox1)
	vbox1.show()

	ag = gtk.GtkAccelGroup()
	itemf = gtk.GtkItemFactory(gtk.GtkMenuBar, "<main>", ag)
	gui["main_window"].add_accel_group(ag)
	itemf.create_items([
		('/_Misc',                      None,               None,           0, '<Branch>'),
		('/_Misc/_Clear',               'Escape',           main_menu,      1, ''),
		('/_Misc/sep1',                 None,               None,           0, '<Separator>'),
#		('/_Misc/Pre_ferences',         None,               None,           3, ''),
#		('/_Misc/sep1',                 None,               None,           0, '<Separator>'),
		('/_Misc/E_xit',                '<alt>X',           main_menu,      2, ''),
		('/_Insert',                    None,               None,           0, '<Branch>'),
		('/_Insert/_Bin value',         '<control>comma',   insert_menu,    1, ''),
		('/_Insert/_ASCII chars',       '<control>period',  insert_menu,    2, ''),
		('/_Insert/_Last result',       '<control>slash',   insert_menu,    3, ''),
		('/_Select',                    None,               None,           0, '<Branch>'),
		('/_Select/_Decimal field',     '<control>1',       select_menu,    1, ''),
		('/_Select/_Hexadecimal field', '<control>2',       select_menu,    2, ''),
		('/_Select/_Octal field',       '<control>3',       select_menu,    3, ''),
		('/_Select/_ASCII field',       '<control>4',       select_menu,    4, ''),
		('/_Select/_Binary field',      '<control>5',       select_menu,    5, ''),
		('/_Select/sep1',               None,               None,           0, '<Separator>'),
		('/_Select/_Clear fields',      '<control>0',       select_menu,    6, ''),
		('/F_unctions',                 None,               None,           0, '<Branch>'),
		('/F_unctions/_Vector length',  None,               functions_menu, 1, ''),
		('/_Help',                      None,               None,           0, '<LastBranch>'),
		('/_Help/Functions list',       'F1',       functions_help_window,      1, ''),
		('/_Help/sep1',                 None,               None,           0, '<Separator>'),
		('/_Help/_About',               None,               help_menu,      1, '')
	])
	menubar = itemf.get_widget('<main>')
	vbox1.pack_start(menubar, expand=gtk.FALSE)
	menubar.show()

	vbox2 = gtk.GtkVBox(spacing=5)
	vbox1.pack_start (vbox2, expand=gtk.TRUE);
	vbox2.show()

	entry = gtk.GtkEntry()
	gui["main_entry"] = entry
	vbox2.pack_start(entry, expand=gtk.FALSE)
	vbox2.set_border_width(4)
	set_font(entry)
	if (config["remember_expression"] == 1):
		entry.set_text(config["last_expression"])
	entry.connect("key_press_event", key_function)
	entry.grab_focus()
	gui["main_entry"].show()

	frame = gtk.GtkFrame()
	vbox2.pack_start(frame)
	frame.show()

	vbox3 = gtk.GtkVBox()
	frame.add(vbox3)
	vbox3.show()

	table = gtk.GtkTable(2, 5, gtk.FALSE)
	table.set_row_spacings(5)
	table.set_col_spacings(5)
	table.set_border_width(10)
	vbox3.pack_start(table)
	table.show()

	for y in range(5):
		label = gtk.GtkLabel(labels[y])
		set_font(label)
		label.show()
		table.attach(label, 0,1, y,y+1)
		entry = gtk.GtkEntry()
		gui[entries[y]] = entry
		entry.set_editable(gtk.FALSE)
		entry.set_usize(260, -2)
		set_font(entry)
		entry.show()
		table.attach(entry, 1,2, y,y+1)

	gui["main_window"].show()

#	print win.get_window().x
#	print win.get_window().y
#	print gtk._root_window().get_position()

	if (config["remember_expression"] == 1):
		result(config["last_expression"])
	else:
		result(0)

#------------------------------------------------------------

def getachr(value):
	fld = 255
	if (config["ascii_only"] == 1):
		fld = 127
	if (value>=32) and (value<=fld):
		return value
	else:
		return "."

#------------------------------------------------------------
# functions

def veclen(x0,y0,x1,y1):
	return sqrt((x1-x0)**2 + (y1-y0)**2)

#------------------------------------------------------------

def bin(value):
	result=0
	for i in range(len(value)):
		if (value[i]!="0" and value[i]!="1"):
			return 0
	for i in range(len(value)):
		if (i>=32):
			return 0
		result=result+((ord(value[len(value)-1-i])-ord("0")) * 1<<i)
	return result

#------------------------------------------------------------

def asc(value):
	result=0
	for i in range(len(value)):
		if (i<4):
			result=result+(ord(value[len(value)-1-i]) * 256**i)
	return result

#------------------------------------------------------------

def ans():
	return eval(gui["entry_dec"].get_text())

#------------------------------------------------------------

def result(value):
	if (value):
		try:
			resl=eval(value)
			resli=int(resl)
		except NameError:
			warning_window("Warning", "Function not found!")
			return 0
		except SyntaxError:
			warning_window("Warning", "Wrong syntax!")
			return 0
		except TypeError:
			warning_window("Warning", "Wrong syntax!")
			return 0
		except ZeroDivisionError:
			warning_window("Warning", "Division by zero!")
			return 0
		except OverflowError:
			warning_window("Warning", "Overflow detected!")
			return 0
	else:
		resl=0
		resli=0
	r_dec = str(resl)
	gui["entry_dec"].set_text(r_dec)
	r_hex = str(hex(resli))
	gui["entry_hex"].set_text(r_hex)
	r_oct = str(oct(resli))
	gui["entry_oct"].set_text(r_oct)
	r_asc="%c%c%c%c" % (getachr((resli>>24) & 255),
    getachr((resli>>16) & 255),
    getachr((resli>>8) & 255), getachr(resli & 255))
	gui["entry_asc"].set_text(r_asc)
	r_bin=""
	for i in range(32):
		k = 31 - i
		chrr=chr(ord("0")+((resli>>k) & 1))
		r_bin=r_bin+chrr
		if (k==8) or (k==16) or (k==24):
			r_bin=r_bin+"."
	gui["entry_bin"].set_text(r_bin)

#------------------------------------------------------------

def set_font(widget):
	style = widget.get_style().copy()
	style.font = gui["fixed_font"]
	widget.set_style(style)

#------------------------------------------------------------
    
def key_function(entry, event):
	if event.keyval == GDK.Return:
		entry.set_text(string.lower(entry.get_text()))
		result(entry.get_text())

#------------------------------------------------------------

def pcalc_get_cfg_dir():
	if os.name == 'posix':
		cfg_dir = os.environ["HOME"]
	elif os.name == 'nt':
		cfg_dir = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
	else:
		sys.exit('Sorry, unknown environment variable for user home on %s OS!' % os.name)
	cfg_dir = cfg_dir + os.sep + ".clay" + os.sep
	return cfg_dir

#------------------------------------------------------------

def pcalc_get_cfg_file():
	return pcalc_get_cfg_dir() + "clarence"

#------------------------------------------------------------

def pcalc_check_config():
	cfg_file = pcalc_get_cfg_file()
	if not os.access(cfg_file, os.F_OK):
		f = open(cfg_file, "w")
#		f.write("win_pos_x=0\n")
#		f.write("win_pos_y=0\n")
		f.write("win_width=400\n")
		f.write("win_height=240\n")
		f.write("window_placement=1\n")         # 0 - none,  1 - center, 2 - mouse
		f.write("ascii_only=0\n")               # 0 - eascii codes (32-255), 1 - ascii (32-127)
		f.write("fixed_font=fixed\n")           # name of fixed font
		f.write("remember_expression=1\n")      # 0 - no,  1 - yes
		f.write("last_expression=\n")           # last expression
		f.flush()
		f.close()

#------------------------------------------------------------

def pcalc_read_config():
	f_lines = open(pcalc_get_cfg_file(), 'r').readlines()
	for line in f_lines:
		fields = string.split(line, '=')
#		if (fields[0] == "win_pos_x"):
#			config["win_pos_x"] = string.atoi(fields[1])
#		if (fields[0] == "win_pos_y"):
#			config["win_pos_y"] = string.atoi(fields[1])
		if (fields[0] == "win_width"):
			config["win_width"] = string.atoi(fields[1])
		if (fields[0] == "win_height"):
			config["win_height"] = string.atoi(fields[1])
		if (fields[0] == "window_placement"):
			config["window_placement"] = string.atoi(fields[1])
		if (fields[0] == "ascii_only"):
			config["ascii_only"] = string.atoi(fields[1])
		if (fields[0] == "fixed_font"):
			config["ffont"] = string.strip(fields[1])
		if (fields[0] == "remember_expression"):
			config["remember_expression"] = string.atoi(fields[1])
		if (fields[0] == "last_expression"):
			config["last_expression"] = string.strip(fields[1])

#------------------------------------------------------------

def pcalc_write_config():
	if gui["main_window"].get_window():
		config["win_width"]=gui["main_window"].get_window().width
		config["win_height"]=gui["main_window"].get_window().height
	if (config["remember_expression"] == 1):
		config["last_expression"]=string.replace(gui["main_entry"].get_text(), "ans()", "0")
	f = open(pcalc_get_cfg_file(), "w")
#	f.write("win_pos_x=%d\n" % config["win_pos_x"])
#	f.write("win_pos_y=%d\n" % config["win_pos_y"])
	f.write("win_width=%d\n" % config["win_width"])
	f.write("win_height=%d\n" % config["win_height"])
	f.write("window_placement=%d\n" % config["window_placement"])
	f.write("ascii_only=%d\n" % config["ascii_only"])
	f.write("fixed_font=%s\n" % config["ffont"])
	f.write("remember_expression=%d\n" % config["remember_expression"])
	f.write("last_expression=%s\n" % config["last_expression"])
	f.flush()
	f.close()

#------------------------------------------------------------

def main():
	pcalc_check_config()
	pcalc_read_config()
	gui["fixed_font"] = gtk.load_font(config["ffont"])
	create_main_window()
	gtk.mainloop()
	pcalc_write_config()

if __name__ == '__main__': main()

