#
########################################################
# python script (GUI) that runs on Nokia n900          #
# and calculates Depth Of Field for photographers      #
########################################################
# Version 0.1                                          #
# Updates can be found on: http://blog.neonatus.net or #
# http://n.neonatus.net/DoFCalc/                       #
######################################################## 

import gtk
import hildon
import math

# list of f-stops and N values
fstops=[('f1', 0.0), ('f1.2', 1.18946), ('f1.4', 1.0), ('f1.6', 1.3333), ('f1.7', 1.5), ('f1.8', 1.6666), ('f2.0', 2.0), ('f2.2', 2.333), ('f2.4', 2.5), ('f2.5', 2.6666), ('f2.8', 3.0), ('f3.2', 3.333) , ('f3.4', 3.5), ('f3.6', 3.666), ('f4.0', 4.0), ('f4.5', 4.333), ('f4.8', 4.5) , ('f5.0', 4.666 ), ('f5.6', 5.0), ('f6.4', 5.333), ('f6.7', 5.5), ('f7.1', 5.666) , ('f8.0', 6.0), ('f9.0', 6.333), ('f9.5', 6.5) , ('f10', 6.666), ('f11', 7.0), ('f12.7', 7.333), ('f13.5', 7.5 ), ('f14.3', 7.666), ('f16', 8.0), ('f18', 8.333 ), ('f19', 8.5 ), ('f20', 8.6666 ) , ('f22', 9.0 ), ('f25', 9.333 ), ('f27', 9.5 ), ('f28', 9.666 ), ('f32', 10.0 ), ('f45', 11.0 ), ('f64', 12.0 ) ]

# confusion diameter (for oly E-510 c == 0.015)
#c=0.015
c=0.000
cd=[ ('35mm', 0.03), ('1.3x DSLR', 0.023), ('1.5x DSLR', 0.02), ('1.6x DSLR', 0.01875), ('2x DSLR', 0.015), ('6x5', 0.05), ('6x6', 0.06), ('6x7', 0.065), ('6x9', 0.075), ('4x5', 0.150) , ('5x7', 0.2 ), ('8x10', 0.3 )]

# global vars (I really don't know enough of python to actually be able to deal without those - but please feel free to educate me)
H = ''
Tot = ''
Front = ''
Behind = ''
After = ''

inputbox_s = ''
inputbox_f = ''
AP = ''
CD = ''
s = 0
f = 0
window = hildon.StackableWindow()

#########################################################################################
def calculate_DoF(fstop):
  global H, Tot, Behind, Front

  N=math.pow(2.0,(fstop/2.0))
 
  #print "N %f, f %f, s %f, c %f" % (N, f, s, c) 
  #######################################################
  # Hyperfocal distance
  H=(f*f)/(N*c)+f

  #Near distance of acceptable sharpness:
  Dn=s*1000.0*(H-f)/(H+(s*1000.0)-2.0*f)
  
  #Far distance of acceptable sharpness:
  Df=s*1000.0*((H)-f)/((H)-(s*1000.0))

  #Total
  Tot=Df-Dn

  # behind subject
  Behind=Df-s*1000.0
  
  # in front of the subject
  Front=s*1000.0-Dn

  return [H, Dn, Df, Tot, Front, Behind]

## display selections after selecting Aperture 
def selection_changed_AP(selector, user_data):
    global AP
    #print dir(selector)
    current_selection = selector.get_current_text()
    AP = current_selection
    print "Current aperture selection : %s" % (current_selection)
    # Create touch selector
    #program = hildon.Program.get_instance()
    window = hildon.StackableWindow()
    selector = create_selector_cd()
    window.add(selector)
    window.set_title("Select Sensor Size");
    #window.connect("destroy", app_quit)
    window.show_all()

## display selections after selecting CD (sensor size) 
def selection_changed_CD(selector, user_data):
    global inputbox_s, inputbox_f, CD
    current_selection = selector.get_current_text()
    CD = current_selection
    print "Current sensor size selection : %s" % (current_selection)
    # Create touch selector
    #program = hildon.Program.get_instance()
    window = hildon.StackableWindow()
    window.set_title("Enter distance and focal length")
    label = gtk.Label("Distance (m)")
    label1 = gtk.Label("Focal length (mm)")
    vbox = gtk.VBox(False, 0)
    inputbox_s = create_input_dist()
    inputbox_f = create_input_dist()
    ok_cancel = create_run_button()
    vbox.pack_start(label, True, True, 0)
    vbox.pack_start(inputbox_s, True, True, 0)
    vbox.pack_start(label1, True, True, 0)
    vbox.pack_start(inputbox_f, True, True, 0)
    vbox.pack_start(ok_cancel, True, True, 0)
 
    window.add(vbox)
    # print dir(window)
    #window.connect("destroy", app_quit)
    window.show_all()

def run_again_button_clicked(button, user_data=None):
    global window
    # get the size of current window stack (open windows) and return to window #1 (aperture selection - first screen)
    stack = window.get_stack()
    #print "Stack.size " + stack.size().__str__()
    stack.pop(stack.size()-1)
    #print "Stack.size " + stack.size().__str__()
 
def run_exit_button_clicked(button, user_data=None):
    gtk.main_quit()

def run_button_clicked(button, user_data=None):
    global inputbox_s, inputbox_f, AP, CD, s, f, c
    s = float(inputbox_s.get_text())
    f = float(inputbox_f.get_text())
    # run DoF calculation
    #print dir(s)
    for elem in fstops:
      if elem[0] == AP:
         APval = elem[1]

    for elem in cd:
      if elem[0] == CD:
         c = elem[1]

    print "Aperture index value %s AP=%s, focal length is %s" % (APval, AP, f.__str__())
    #selected=fstops[index][1]
    result = calculate_DoF(APval)
    #  return [H, Dn, Df, Tot, Front, Behind]
    Df = result[2]
    if Df < 0:
      Df = 'Inf'
    else:
      Df = round(Df/1000,2).__str__() + "m"
    Dn = result[1]
    if Dn < 0:
       Dn = 'Inf'
    else:
       Dn = round(Dn/1000,2).__str__() + "m"
    Tot = result[3]
    if Tot < 0:
       Tot = 'Inf'
    else:
       Tot = round(Tot/1000,2).__str__() + "m"
    Behind = result[5]
    if Behind < 0:
       Behind = 'Inf'
    else:
       Behind = round(Behind/1000,2).__str__() + "m"
    Front = result[4]
    if Front < 0:
       Front = 'Inf'
    else:
       Front = round(Front/1000,2).__str__() + "m"

    print "Button clicked with distance %d and focal length %d" % (s, f)
    print "And the result is %s" % (result[0])
    # display results
    window = hildon.StackableWindow()
    window.set_title("Results")
    label_title = gtk.Label("Depth of Field %s @ %d mm -> %d m" % (AP, f, s))
    vbox = gtk.VBox(False, 0)
    vbox.pack_start(label_title)
    strlg = 12
    str1 = "Near limit" 
    str2 = 'Far limit'
    str3 = 'Total'
    str4 = 'In front'
    str5 = 'Behind'
    rezdisplay = gtk.Label("%s%s: %8s" % (str1, (strlg-len(str1))*'.', Dn + "\n") +
                           "%s%s: %8s" % (str2, (1+strlg-len(str2))*'.', Df + "\n") +
                           "%s%s: %8s" % (str3, (3+strlg-len(str3))*'.', Tot + "\n") +
                           "%s%s: %8s" % (str4, (1+strlg-len(str4))*'.', Front + "\n") +
                           "%s%s: %8s" % (str5, (1+strlg-len(str5))*'.', Behind + "\n") +
                           "%21.21s %s" % ("Hyperfocal distance: ", round(result[0]/1000,2).__str__() + "m\n") + "%21.21s %s" % ("Circle of confusion: ", c.__str__() + "mm\n"))
    vbox.pack_start(rezdisplay) 
    run_again_button = create_run_again_button()
    vbox.pack_start(run_again_button, True, True, 0)
    exit_button = create_exit_button()
    vbox.pack_start(exit_button, True, True, 0)
    window.add(vbox)
    #print dir(window)
    #window.connect("destroy", app_quit)
    window.show_all()
   
def show_confirmation_note_with_buttons(parent, txtString):
    note = hildon.hildon_note_new_confirmation(parent, txtString)
#    note.add_buttons("ACCEPT", gtk.RESPONSE_OK, \
#                     "CANCEL", gtk.RESPONSE_CANCEL, \
#                     "DELETE", gtk.RESPONSE_DELETE_EVENT)
    response = gtk.Dialog.run(note)

    if response == gtk.RESPONSE_OK:
	print "show_information_note: gtk.RESPONSE_OK"
        return True 
    if response == gtk.RESPONSE_CANCEL:
	print "show_information_note: gtk.RESPONSE_CANCEL" 
        return False
    if response == gtk.RESPONSE_DELETE_EVENT:
        print "show_information_note: gtk.RESPONSE_DELETE_EVENT"
        gtk.main_quit()

def create_run_button():
    button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
    button.set_title("Run")
    button.connect("clicked", run_button_clicked)
    return button

def create_run_again_button():
    button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
    button.set_title("Run again")
    button.connect("clicked", run_again_button_clicked)
    return button

def create_exit_button():
    button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH, hildon.BUTTON_ARRANGEMENT_VERTICAL)
    button.set_title("Exit the program")
    button.connect("clicked", run_exit_button_clicked)
    return button

def create_input_dist():
    entry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
    entry.set_placeholder("Enter value")
    return entry

def create_selector_aperture():
    #Create a HildonTouchSelector with a single text column
    selector = hildon.TouchSelector(text = True)
 
    # Set a handler to "changed" signal
    selector.connect("changed", selection_changed_AP)
 
    # Populate selector (with aperture selection)
    for element in fstops:
        #print element #DEBUG
        selector.append_text(element[0]) #,"aperture"))
 
    # Set selection mode to allow single selection
    selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
 
    return selector
 
def create_selector_cd():
    #Create a HildonTouchSelector with a single text column
    selector = hildon.TouchSelector(text = True)
 
    # Set a handler to "changed" signal
    selector.connect("changed", selection_changed_CD)
 
    # Populate selector (with aperture selection)
    for element in cd:
        #print element #DEBUG
        selector.append_text(element[0]) #,"aperture"))
 
    # Set selection mode to allow single selection
    selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
 
    return selector

def app_quit(widget, data=None):
    gtk.main_quit()
 
def main():
    program = hildon.Program.get_instance()
    gtk.set_application_name("DoF Calc 4 Maemo")
 
    program.add_window(window)
 
    # Create touch selector
    selector = create_selector_aperture()
    window.add(selector)
    window.set_title("Select Aperture");

 
    window.connect("destroy", app_quit)
 
    window.show_all()
 
    gtk.main()
 
if __name__ == "__main__":
    main()

