#!/usr/bin/python

program_name = "pyrpn"
program_version = "0.1"


print "Loading GTK..."

from gtk import *

print "Loading GTK...  done."

def runqueue():
  while events_pending():
    mainiteration()


#
# Config file handling, and defaults...
#

######################################################################
# Config class:
#
def is_config(obj):
  return hasattr(obj,'is_config') and obj.is_config

class config:
  is_config = 1

  def __init__(self):
    import os
    self.options = {}
    self.usrfilename = "%s/.%s.conf" % (os.environ["HOME"], program_name)
    self.globalfilename = "/etc/%s/%s.conf" % (program_name, program_name)
    self.namespace = {
      "options" : self.options,
      }

  ##################################################
  #
  def load(self):
    import os.path
    for name in [self.globalfilename, self.usrfilename]:
      if os.path.exists(name):
        self.parse(name)

  ##################################################
  #
  def parse(self, filename):
    #print "parsing %s..." % filename
    
    try:
      execfile(filename, self.namespace)
    except:
      print "Error in config file %s" % filename
      import traceback, sys
      foo = traceback.format_exception(sys.exc_type,
                                       sys.exc_value,
                                       sys.exc_traceback)
      for item in foo:
        item = item[:-1]
        print item
      print "Press Enter to continue..."
      raw_input()



############################################################
#
#
_ButtonChoice_clicked = None

def _ButtonChoiceButtonCB(button):
  global _ButtonChoice_clicked
  _ButtonChoice_clicked = button
  mainquit()


############################################################
#
#
def ButtonChoiceWindow(title, text, buttons, default=0, long=0,
                       transient=None):
  global _ButtonChoice_clicked
  _ButtonChoice_clicked = None

  win = GtkWindow()
  if transient:
    win.set_transient_for(transient)
  win.connect("destroy", mainquit)
  win.set_position(WIN_POS_CENTER)
  win.set_modal(1)
  win.set_title(title)
  win.set_border_width(5)

  v = GtkVBox()

  #if len(text) < 300:
  if not long:

    l = GtkLabel(text)
    l.set_line_wrap(1)
    l.show()
    v.add(l)
    
  else:  #if len(text) > 300:
    
    lbox = GtkHBox()
    l = GtkText(None, None)
    l.set_word_wrap(1)
    l.set_line_wrap(1)
    scroll = GtkVScrollbar(l.get_vadjustment())
    lbox.add(l)
    lbox.add(scroll)
    l.insert(None, None, None, text)
    l.set_usize(400, 300)
    v.add(lbox)
    

  box = GtkHButtonBox()
  box.set_border_width(10)


  buttonlist = []
  i = 0
  for item in buttons:
    if type(item) == type((1,2)):
      label, value = item
    else:
      label = value = item


    b = GtkButton(label)
    buttonlist.append([label, value, b])
    b.connect("clicked", _ButtonChoiceButtonCB)

    if default >= 0  and  i == default:
      b.set_flags(CAN_DEFAULT)
      b.set_flags(HAS_DEFAULT)
      box.add(b)
    #if i != default  and  default >= 0:
    #  #box.add(b)
    #  f = GtkFrame()
    #  f.set_border_width(2)
    #  f.set_shadow_type(SHADOW_NONE)
    #  f.add(b)
    #  box.add(f)
    else:
      f = GtkFrame()
      f.set_border_width(2)
      f.set_shadow_type(SHADOW_NONE)
      f.add(b)
      box.add(f)

    i = i + 1

  box.show()
  v.add(box)

  v.show()
  win.add(v)

  win.show_all()
  mainloop()

  retval = None

  for item in buttonlist:
    label, value, b = item
    if b == _ButtonChoice_clicked:
      retval = value

  win.hide()
  win.unrealize()
  del win

  return retval





######################################################################
#
# Basic:
#  * +, -, *, /
#  - modulus
#  * sin, cos, tan
#  * asin, acos, atan
#  - sinh, cosh, tanh
#  - asinh, acosh, atanh
#  - log, ln, e^x, 10^x
#  * x^y, x^2, sqrt(x)
#  - sqrt, y^x, 1/x; x^2, xroot, 10^x, log, e^x, ln
#  - inv  (1/x)
#  - min / max
#  - abs, sign
#  - ipart, fpart, floor, ceil
#  - random...
#
# Integer / Logic:
#  - ->base (num, from, to)
#  - lshift, rshift, rroll, lroll
#  - atonl, ntoal
#  - and, or, xor, not
#  - &, |, ^
#  - ->int, ->float
#
# Stack:
#  * drop
#  + swap
#  * dup
#  - clear
#  * last / undo
#  - roll (roll by x or -x items)
#  - pick
#  - edit
#  - dupn, dropn
#
# Extended:
#  - arrays, lists, matrices
#
# Datatypes, units:
#  - explicit datatyping (type, value, units)
#  - datatype conversion (->int, etc)
#  - unit conversion
#    - deg, rad
#    - inch, foot, yard, mile, mm, cm, m, km
#    - g, kg, lbs, ...
#    - ...
#
# Algebra:
#  - eval expressions
#
# Statistics / probability:
#  - combination
#  - permutation
#  - !
#  - rand (0.0 - 1.0)
#
# Financial
#
# Registers?
#
# Constants:
#  - e, i, pi, symbolic and numeric versions of each?
#
# Storage:
#  - directories, filesystem, etc...
#  - sto
#  - purge
#
# Programming:
#  - <<, >>
#  - if
#  - for
#  - while
#
# Options / modes:
#  - precision
#  - degrees, radians, grads
#  - std / fixed / sci floats
#
class stack:

  def __init__(self, cfg, parent=None):
    self.data = []
    self.parent = parent
    self.cfg = cfg

    self.functions = {
      "drop" : self.drop,
      "swap" : self.swap,
      "dup" : self.dup,
      "+" : self.add,
      "-" : self.subtract,
      "*" : self.multiply,
      "/" : self.divide,
      "neg" : self.negate,
      "undo" : self.undo,
      "sin" : (self.maths, ["sin"]),
      "cos" : (self.maths, ["cos"]),
      "tan" : (self.maths, ["tan"]),
      "asin" : (self.maths, ["asin"]),
      "acos" : (self.maths, ["acos"]),
      "atan" : (self.maths, ["atan"]),
      "sinh" : (self.maths, ["sinh"]),
      "cosh" : (self.maths, ["cosh"]),
      "tanh" : (self.maths, ["tanh"]),
      "asinh" : (self.maths, ["asinh"]),
      "acosh" : (self.maths, ["acosh"]),
      "atanh" : (self.maths, ["atanh"]),
      "sqrt" : (self.maths, ["sqrt"]),
      "square" : self.square,
      "pow" : self.pow,
      "angle_unit" : self.angle_unit,
      "precision" : self.precision,
      }

  ##################################################
  #
  def execute(self, func):
    try:
      if func != self.undo:
        self.backup()
      if type(func) == type((1,2)):
        f = func[0]
        args = func[1]
        apply(f, args)
      else:
        func()
    except Exception:
      import sys
      ei = sys.exc_info()
      text = "%s: %s" % (ei[0], ei[1])
      text = text[11:]
      self.error("%s" % text)

  ##################################################
  #
  def operate(self, func, force=0):

    if self.functions.has_key(func)  or  force:
      self.execute(self.functions[func])

    else:
      self.push(func)

  ##################################################
  #
  def dump(self):
    i = len(self.data) - 1
    while i >= 0:
      print "%s: %s" % (i+1, repr(self.data[i]))
      i = i - 1

  ##################################################
  # Return stack to previous state
  # (only works once)
  def backup(self):
    self.old_data = self.data[:]

  ##################################################
  # Return stack to previous state
  # (only works once)
  def undo(self):
    foo = self.old_data
    self.old_data = self.data
    self.data = foo

  ##################################################
  #
  def retrieve(self, amount):
    result = []
    for i in xrange(amount):
      try:
        val = self.data[i]
      except:
        val = ''
      result.insert(0, val)

    return result

  ##################################################
  #
  def push(self, val):
    self.backup()
    self.data.insert(0, val)

  ##################################################
  #
  def error(self, text):
    title = "Stack Error"
    buttons = [("D'oh!", "ok")]
    ButtonChoiceWindow(title, text, buttons, transient=self.parent)

  ##################################################
  #
  def drop(self):
    del self.data[0]

  ##################################################
  #
  def swap(self):
    a = self.data[0]
    b = self.data[1]
    self.data[0] = b
    self.data[1] = a
      
  ##################################################
  #
  def dup(self):
    a = self.data[0]
    self.push(a)
      

  ##################################################
  #
  def add(self):
    a = self.data[0]
    b = self.data[1]
    c = b + a
    del self.data[1]
    self.data[0] = c

  ##################################################
  #
  def subtract(self):
    a = self.data[0]
    b = self.data[1]
    c = b - a
    del self.data[1]
    self.data[0] = c

  ##################################################
  #
  def negate(self):
    a = self.data[0]
    a = -a
    self.data[0] = a

  ##################################################
  #
  def multiply(self):
    a = self.data[0]
    b = self.data[1]

    # coerce floats to ints for string multiplication
    if type(a) == type("foo"):
      if type(b) == type(1.1):
        b = int(b)
    if type(b) == type("foo"):
      if type(a) == type(1.1):
        a = int(a)
        
    c = b * a
    del self.data[1]
    self.data[0] = c

  ##################################################
  #
  def divide(self):
    a = self.data[0]
    b = self.data[1]
    c = b / a
    del self.data[1]
    self.data[0] = c

  ##################################################
  # Execute simple one-argument functions from the
  # math module...
  # a(sin|cos|tan)h seem to be missing
  #
  def maths(self, which):
    import math
    funcs = {
      "sin" : math.sin,
      "cos" : math.cos,
      "tan" : math.tan,
      "asin" : math.asin,
      "acos" : math.acos,
      "atan" : math.atan,
      "sinh" : math.sinh,
      "cosh" : math.cosh,
      "tanh" : math.tanh,
      #"asinh" : math.asinh,
      #"acosh" : math.acosh,
      #"atanh" : math.atanh,
      "sqrt" : math.sqrt,
      }
    a = self.data[0]
    func = funcs[which]
    self.data[0] = func(a)

  ##################################################
  #
  def square(self):
    import math
    a = self.data[0]
    self.data[0] = a*a

  ##################################################
  #
  def pow(self):
    import math
    a = self.data[0]
    b = self.data[1]
    del self.data[1]
    self.data[0] = b**a

  ##################################################
  #
  def angle_unit(self):
    a = self.data[0]
    if a in ["degrees", "deg"]:
      self.cfg.options["angle unit"] = "degrees"
    elif a in ["radians", "rad"]:
      self.cfg.options["angle unit"] = "radians"
    else:
      raise ValueError, "Invalid angle unit"

    del self.data[0]

  ##################################################
  #
  def precision(self):
    a = self.data[0]
    a = int(a)
    if a < 0:
      raise ValueError, "Precision must be non-negative integer"
    self.cfg.options["precision"] = a

    del self.data[0]

######################################################################
#
# misc todo:
# - insert commas in numbers > 3 digits
#
# Possible frame/tab configuration:
# - basic: numpad, main ops
# - stack
# - alpha (fitaly)
# - trig
# - programming
# - matrix
# - units
# - file manager (always visible?)
#
class calculator:

  def __init__(self, cfg):
    self.cfg = cfg

    self.win = GtkWindow()
    self.win.connect("destroy", self.destroy)
    self.win.set_title("%s %s" % (program_name, program_version))
    self.win.set_default_size(self.cfg.options["width"],
                              self.cfg.options["height"])

    self.stack = stack(self.cfg, self.win)

    self.vbox = GtkVBox()
    self.win.add(self.vbox)

    self.make_menus()
    self.make_stacklist()
    self.make_tophalf()
    self.make_bottomhalf()


  ##################################################
  #
  def make_menus(self):
    self.ag = GtkAccelGroup()
    self.win.add_accel_group(self.ag)
    self.itemf = GtkItemFactory(GtkMenuBar, "<main>", self.ag)
    foo = [("/_Kill", None, None, 0, "<Branch>")]
    self.itemf.create_items([
      ("/File", None,  None,  0,  "<Branch>"),
      ("/File/_Quit", "<Control>Q",  self.destroy,  0,  ""),
      ])
    self.menubar = self.itemf.get_widget("<main>")
    self.vbox.pack_start(self.menubar, fill=1, expand=0)

    foo = GtkItemFactory(GtkMenuBar, "<keydefs>", self.ag)
    foo.create_items([
      ("/foo",       None, None, 0, "<Branch>"),
      #("/foo/quit",  "<Control>Q", self.destroy, 0, ""),
      ])


  ##################################################
  #
  def make_stacklist(self):
    sw = GtkScrolledWindow()
    sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
    sw.set_placement(CORNER_BOTTOM_RIGHT)

    self.stack_lb = GtkCList(2)
    #self.stack_lb.connect("select-row", self._stacklist_select_cb)
    self.stack_lb.set_selection_mode(SELECTION_BROWSE)
    self.stack_lb.set_column_justification(0, JUSTIFY_RIGHT)
    self.stack_lb.set_column_justification(1, JUSTIFY_RIGHT)
    self.stack_lb.set_column_auto_resize(0, 1)
    self.stack_lb.set_column_auto_resize(1, 1)
    sw.add(self.stack_lb)
    
    self.vbox.pack_start(sw, fill=TRUE, expand=TRUE)

    self.entry = GtkEntry()
    self.entry.connect("activate", self._run_entry)
    self.vbox.add(self.entry, fill=0, expand=0)

  ##################################################
  #
  def update_stack(self):
    self.stack_lb.freeze()
    self.stack_lb.clear()
    for i in xrange(len(self.stack.data)):
      val = self.stack.data[i]
      # Handle known types...
      # Long integer
      if type(val) == type(1L):
        d = "%s" % repr(val)
        d = d[:-1]
      # Float
      elif type(val) == type(1.0):
        if self.cfg.options["precision"]:
          format = "%%.%if" % (self.cfg.options["precision"])
          d = format % val
        else:
          d = repr(val)
      # Everything else...
      else:
        d = repr(self.stack.data[i])
      self.stack_lb.insert(0, ["%s:" % (i), d])

    self.stack_lb.thaw()

    # scrollbar needs to automatically scroll to the bottom
    a = self.stack_lb.get_vadjustment()
    if a.upper > a.page_size:
      scrollto = a.upper - a.page_size
      #print "scrollto: %s" % scrollto
      a.set_value(scrollto)
    self.entry.grab_focus()

  ##################################################
  #
  def make_tophalf(self):

    nb = GtkNotebook()
    self.vbox.add(nb)

    t = self.make_trig()
    nb.append_page(t, GtkLabel("trig"))

    t = self.make_modes()
    nb.append_page(t, GtkLabel("modes"))


  ##################################################
  #
  def make_trig(self):
    t = GtkTable(2,6)
    b = GtkButton("sin")
    b.connect("clicked", self._operate, "sin")
    t.attach(b, 0, 1, 0, 1)
    b = GtkButton("cos")
    b.connect("clicked", self._operate, "cos")
    t.attach(b, 1, 2, 0, 1)
    b = GtkButton("tan")
    b.connect("clicked", self._operate, "tan")
    t.attach(b, 2, 3, 0, 1)

    b = GtkButton("asin")
    b.connect("clicked", self._operate, "asin")
    t.attach(b, 0, 1, 1, 2)
    b = GtkButton("acos")
    b.connect("clicked", self._operate, "acos")
    t.attach(b, 1, 2, 1, 2)
    b = GtkButton("atan")
    b.connect("clicked", self._operate, "atan")
    t.attach(b, 2, 3, 1, 2)

    b = GtkButton("sinh")
    b.connect("clicked", self._operate, "sinh")
    t.attach(b, 3, 4, 0, 1)
    b = GtkButton("cosh")
    b.connect("clicked", self._operate, "cosh")
    t.attach(b, 4, 5, 0, 1)
    b = GtkButton("tanh")
    b.connect("clicked", self._operate, "tanh")
    t.attach(b, 5, 6, 0, 1)

    #b = GtkButton("asinh")
    #b.connect("clicked", self._operate, "asinh")
    #t.attach(b, 3, 4, 1, 2)
    #b = GtkButton("acosh")
    #b.connect("clicked", self._operate, "acosh")
    #t.attach(b, 4, 5, 1, 2)
    #b = GtkButton("atanh")
    #b.connect("clicked", self._operate, "atanh")
    #t.attach(b, 5, 6, 1, 2)

    return t
  
  ##################################################
  #
  def make_modes(self):
    t = GtkTable(2,4)

    b = GtkButton("precision")
    b.connect("clicked", self._operate, "precision")
    t.attach(b, 0, 1, 0, 1)

    b = GtkButton()
    text = "deg"
    if self.cfg.options["angle unit"] in ["rad", "radians"]:
      text = "rad"
    self._degrad_label = GtkLabel(text)
    b.add(self._degrad_label)
    b.connect("clicked", self._degrad)
    t.attach(b, 1, 2, 0, 1)

    #t.attach(GtkButton(), 1, 2, 0, 1)
    #t.attach(GtkButton(), 2, 3, 0, 1)
    #t.attach(GtkButton(), 3, 4, 0, 1)
    #t.attach(GtkButton(), 0, 1, 1, 2)
    #t.attach(GtkButton(), 1, 2, 1, 2)
    #t.attach(GtkButton(), 2, 3, 1, 2)
    #t.attach(GtkButton(), 3, 4, 1, 2)

    return t



  ##################################################
  #
  def make_bottomhalf(self):
    nb = GtkNotebook()
    self.vbox.add(nb)

    numpad = self._make_numpad()
    nb.append_page(numpad, GtkLabel("num"))

    alphapad = self._make_alphapad()
    nb.append_page(alphapad, GtkLabel("alpha"))
    
    #grapher = GtkFrame()
    #nb.append_page(grapher, GtkLabel("graph"))
    
  ##################################################
  #
  def _make_numpad(self):
    hb = GtkHBox()
    hb.set_homogeneous(0)
    hb.set_spacing(10)

    t0 = GtkTable(4, 2)
    b = GtkButton("undo")
    b.connect("clicked", self._operate, "undo")
    t0.attach(b, 0, 2, 0, 1)

    b = GtkButton("x^2")
    b.connect("clicked", self._operate, "square")
    t0.attach(b, 0, 1, 1, 2)
    b = GtkButton("sqrt")
    b.connect("clicked", self._operate, "sqrt")
    t0.attach(b, 1, 2, 1, 2)

    b = GtkButton("x^y")
    b.connect("clicked", self._operate, "pow")
    t0.attach(b, 0, 1, 2, 3)

    #t0.attach(GtkButton(), 0, 1, 1, 2)
    #t0.attach(GtkButton(), 0, 1, 2, 3)
    t0.attach(GtkButton(), 0, 1, 3, 4)
    #t0.attach(GtkButton(), 1, 2, 0, 1)
    #t0.attach(GtkButton(), 1, 2, 1, 2)
    t0.attach(GtkButton(), 1, 2, 2, 3)
    t0.attach(GtkButton(), 1, 2, 3, 4)

    hb.add(t0)
    
    
    t = GtkTable(4,3)
    t.set_homogeneous(1)
    
    b0 = GtkButton("0")
    bdot = GtkButton(".")
    b1 = GtkButton("1")
    b2 = GtkButton("2")
    b3 = GtkButton("3")
    b4 = GtkButton("4")
    b5 = GtkButton("5")
    b6 = GtkButton("6")
    b7 = GtkButton("7")
    b8 = GtkButton("8")
    b9 = GtkButton("9")
    
    t.attach(bdot, 1, 2, 3, 4)
    t.attach(b0, 0, 1, 3, 4)
    t.attach(b1, 0, 1, 2, 3)
    t.attach(b2, 1, 2, 2, 3)
    t.attach(b3, 2, 3, 2, 3)
    t.attach(b4, 0, 1, 1, 2)
    t.attach(b5, 1, 2, 1, 2)
    t.attach(b6, 2, 3, 1, 2)
    t.attach(b7, 0, 1, 0, 1)
    t.attach(b8, 1, 2, 0, 1)
    t.attach(b9, 2, 3, 0, 1)

    bdot.connect("clicked", self._type_into_entry, ".")
    b0.connect("clicked", self._type_into_entry, "0")
    b1.connect("clicked", self._type_into_entry, "1")
    b2.connect("clicked", self._type_into_entry, "2")
    b3.connect("clicked", self._type_into_entry, "3")
    b4.connect("clicked", self._type_into_entry, "4")
    b5.connect("clicked", self._type_into_entry, "5")
    b6.connect("clicked", self._type_into_entry, "6")
    b7.connect("clicked", self._type_into_entry, "7")
    b8.connect("clicked", self._type_into_entry, "8")
    b9.connect("clicked", self._type_into_entry, "9")

    bneg = GtkButton("+/-")
    bneg.connect("clicked", self._operate, "neg")
    t.attach(bneg, 2, 3, 3, 4)

    hb.add(t, fill=1, expand=1)

    t2 = GtkTable(4,2)
    t2.set_homogeneous(1)

    #t2.attach(GtkButton(), 0, 1, 0, 1)
    #t2.attach(GtkButton(), 1, 2, 0, 1)
    bdrop = GtkButton("<--")
    bdrop.connect("clicked", self._drop)
    t2.attach(bdrop, 0, 2, 0, 1)
    bplus = GtkButton("+")
    bplus.connect("clicked", self._operate, "+")
    t2.attach(bplus, 0, 1, 1, 2)
    bminus = GtkButton("-")
    bminus.connect("clicked", self._operate, "-")
    t2.attach(bminus, 0, 1, 2, 3)
    bmult = GtkButton("*")
    bmult.connect("clicked", self._operate, "*")
    t2.attach(bmult, 1, 2, 1, 2)
    bdiv = GtkButton("/")
    bdiv.connect("clicked", self._operate, "/")
    t2.attach(bdiv, 1, 2, 2, 3)
    bret = GtkButton("Enter")
    bret.connect("clicked", self._run_entry)
    t2.attach(bret, 0, 2, 3, 4)


    hb.add(t2)

    return hb


  ##################################################
  #
  def _make_alphapad(self):
    self._shifted = 0

    rows = self.cfg.options["keymap"]
    wid = len(rows[0])
    hgt = len(rows)
    
    t = GtkTable(hgt,wid)
    t.set_homogeneous(1)

    y = 0
    for row in rows:
      x = 0
      for l in row:
        if l != " ":
          b = GtkButton(l)
          b.connect("clicked", self._type, l)
          t.attach(b, x, x+1, y, y+1)
        x = x + 1
      y = y + 1

    b = GtkButton(" ")
    b.connect("clicked", self._type, " ")
    bpos = [b] ; bpos.extend(self.cfg.options["space 1 pos"])
    apply(t.attach, bpos)
    b = GtkButton(" ")
    b.connect("clicked", self._type, " ")
    bpos = [b] ; bpos.extend(self.cfg.options["space 2 pos"])
    apply(t.attach, bpos)

    self.shift_label1 = GtkLabel("shf")
    self.shift_label2 = GtkLabel("shf")
    b = GtkButton()
    b.add(self.shift_label1)
    b.connect("clicked", self._type, "shift")
    bpos = [b] ; bpos.extend(self.cfg.options["shift 1 pos"])
    apply(t.attach, bpos)
    b = GtkButton()
    b.add(self.shift_label2)
    b.connect("clicked", self._type, "shift")
    bpos = [b] ; bpos.extend(self.cfg.options["shift 2 pos"])
    apply(t.attach, bpos)
    
    b = GtkButton("<--")
    b.connect("clicked", self._drop)
    bpos = [b] ; bpos.extend(self.cfg.options["<-- pos"])
    apply(t.attach, bpos)
    
    b = GtkButton("TAB")
    b.connect("clicked", self._type, "\t")
    bpos = [b] ; bpos.extend(self.cfg.options["tab pos"])
    apply(t.attach, bpos)

    b = GtkButton("RET")
    b.connect("clicked", self._run_entry)
    bpos = [b] ; bpos.extend(self.cfg.options["ret pos"])
    apply(t.attach, bpos)

    return t


  ##################################################
  #
  def _operate(self, b, func):
    # Automatically "press enter" if entry is not empty
    t = self.entry.get_text()
    if t != "":
      self.entry.activate()

    self.stack.operate(func)
    self.update_stack()

  ##################################################
  #
  def _type_into_entry(self, b, text):
    t = self.entry.get_text()
    p = self.entry.get_position()
    t = "%s%s%s" % (t[:p], text, t[p:])
    self.entry.set_text(t)
    self.entry.set_position(p+len(text))
    self.entry.grab_focus()

  ##################################################
  #
  def _type(self, b, text):
    if self._shifted:
      caps = self.cfg.options["caps"]
      if caps.has_key(text):
        text = caps[text]

    if len(text) > 1:
      if text == "shift":
        if self._shifted == 0:
          self._shift(1)
        elif self._shifted == 1:
          self._shift(2)
        else:
          self._shift(0)
    else:
      self._type_into_entry(b, text)
      if self._shifted == 1:
        self._shift(0)

  ##################################################
  #
  def _shift(self, val):
    self._shifted = val
    if val == 0:
      self.shift_label1.set_text("shf")
      self.shift_label2.set_text("shf")
    elif val == 1:
      self.shift_label1.set_text("Shf")
      self.shift_label2.set_text("Shf")
    elif val > 1:
      self.shift_label1.set_text("SHF")
      self.shift_label2.set_text("SHF")

  ##################################################
  #
  def _run_entry(self, *args):
    text = self.entry.get_text()
    if not text:  text = "dup"

    # Attempt to convert into a non-text datatype
    try:
      if "." in text:
        text = float(text)
      else:
        text = long(text)
    except:
      pass

    # Execute command
    self.stack.operate(text)

    self.entry.set_text("")
    self.update_stack()

  ##################################################
  #
  # TODO: make this behave better at beginning/end of line
  def _entry_backspace(self, *args):
    t = self.entry.get_text()
    p = self.entry.get_position()
    if t != ""  and  p > 0:
      t = t[:p-1] + t[p:]
      self.entry.set_text(t)
      self.entry.set_position(p-1)
      self.entry.grab_focus()

  ##################################################
  #
  def _degrad(self, b, *args):
    unit = "degrees"
    l = self._degrad_label
    l.set_text("deg")
    if self.cfg.options["angle unit"] == "degrees":
      unit = "radians"
      l.set_text("rad")
    self._operate(None, unit)
    self._operate(None, "angle_unit")

  ##################################################
  #
  def _drop(self, *args):
    t = self.entry.get_text()
    if t != "":
      self._entry_backspace()
    else:
      self._operate(None, "drop")

  ##################################################
  #
  def run(self):

    self.win.show_all()
    self.update_stack()
    mainloop()

  ##################################################
  #
  def destroy(self, *args):
    self.win.hide()
    self.win.unrealize()
    mainquit()


######################################################################
#
def run(args):
  cfg = config()
  cfg.load()
  
  c = calculator(cfg)
  c.run()


######################################################################
#
if __name__ == "__main__":
  import sys
  run(sys.argv[1:])
