from tkinter import *  # Module for Graphic User Interface build and operation
from tkinter import messagebox
from uarm.wrapper import SwiftAPI  # Module with all the uArm functions supplied by UFACTORY, uArm swift pro makers
from PIL import Image  # Module processing colored images as part of print pipette
import numpy as np
from tkinter.filedialog import askopenfilename
from tkinter.filedialog import asksaveasfile

# Pipettor Shortcuts for manual operations
KEY = 'U = Up\n' \
      'J = Down\n' \
      'H = Forward\n' \
      'Y = Backwards\n' \
      'N = Left\n' \
      'M = Right\n' \
      'Q = Turn Left\n' \
      'W = Turn Right\n' \
      'I = Pipette Draw\n' \
      'K = Pipette Dispense\n' \
      'P = Parking\n' \
      'Use CapsLock to switch between Fast and Slow movement'

# Define the Uarm object and its port, "do_not_open" means it will not connect automatically
swift = SwiftAPI(filters={'hwid': 'USB VID:PID=2341:0042'}, do_not_open=True)

SHOW_MANUAL_KEY = True  # used to determine if a popup message will po with activation of manual mode
MANUAL_MODE = False  # used to enable manual mode keys

# Constants for speed at different movements:
OSM = 50  # overall speed multiplier
MANUAL_HIGH_SPEED = 10000
MANUAL_LOW_SPEED = 1000
MANUAL_ANGEL_LOW_SPEED = 2000
PIPETTE_AUTO_SPEED = 1200000
AUTO_SPEED = 500  # when used it is multiplied by the overall speed multiplier
GET_LOW_SPEED = 40  # when used it is multiplied by the overall speed multiplier

# used in all movements of the arm, put True to make the arm wait for confirmation before sending the next one..
WAITTF = False

E_DRAW_AMOUNT = 0.0 # HOW MUCH LIQUID TO DRAW WHEN PIPETTE IS EMPTY WHILE PRINTTING (MM not! ul)
E_DRAW_VAL = 0  # INDEX FOR MODULE TO TAKE LIQUID FROM

CURRENT_TIP = 4  # [0=CP10,1=CP25,2=CP50,3=CP100,4=CP250,5=CP1000]
tip_list = []  # list of all pipette tips models available

MAP_Y_ORIGIN = 379  # used for placing a modules pic on the map
MAP_X_ORIGIN = 266  # used for placing a modules pic on the map
MAP_PIXELS_PER_MM = 1.5  # used for placing a modules pic on the map
status_update_after_id = 1  # used to store the ID of the status bar refresh loop (in order to stop it when needed)

arm_pos = [0.0, 0.0, 0.0]
p_pos = -999.9  # pipette position tracking, there is no easy way to read the E0 stepper position

module_val = 0  # global variable to transfer data from command windows
amount_val = 0.0  # global variable to transfer data from command windows

modules_list = []  # this list stores all modules available on the workspace
commands_list_functions = []  # list of commands in an executable format, acts as shadow for the viable commands list
CLEAR_AFTER_RUN = False  # used to determine whiter or not to delete all function from command list after "run list"


def set_speed_mul(input):  # this is the function for the speed controlling bar
    global OSM  # overall speed multiplier
    OSM = float(speed_bar.get())


# Delete module #
def delete_module_selected():  # Deletes module but does not delete the related functions
    selection = modulelist.curselection()
    index = selection[0]
    print(selection)
    print(index)
    delete_module(index)

def delete_module(index):  # Deletes module from canvas, modules list and displied list
    baseMap.delete(modules_list[index].imageTitle)
    baseMap.delete(modules_list[index].imageID)
    del modules_list[index]
    modulelist.delete(index)


# SAVE FUNCTIONS #
def save_config(operationSave=0):
    global file
    # Opens a dialog window to pic name and location for the saved file
    file = asksaveasfile(title='Save Modules Configuration',
                         initialdir='modules/configurations',
                         defaultextension='.txt')
    file.write(str(len(modules_list)))  # first data is how many modules are save in the file
    # write all the modules data in order
    for i in range(len(modules_list)):
        file.write("\n" + str(modules_list[i].originX))
        file.write("\n" + str(modules_list[i].originY))
        file.write("\n" + str(modules_list[i].travelZ))
        file.write("\n" + str(modules_list[i].surfaceZ))
        file.write("\n" + str(modules_list[i].incX))
        file.write("\n" + str(modules_list[i].incY))
        file.write("\n" + str(modules_list[i].image_address))
        file.write("\n" + str(modules_list[i].name))
        file.write("\n" + str(modules_list[i].rows))
        file.write("\n" + str(modules_list[i].cols))
        file.write("\n" + str(modules_list[i].is_dispose))
        file.write("\n" + str(modules_list[i].is_draw))
        file.write("\n" + str(modules_list[i].is_Dispense))
        file.write("\n" + str(modules_list[i].is_GetTip))
        file.write("\n" + str(modules_list[i].is_Print))
    if not operationSave:
        file.close()


def save_opera():
    global file
    save_config(operationSave=1)
    # saving the number of functions in lists:
    num_commands = len(commands_list_functions)
    file.write('\n\n' + str(num_commands))
    file.write('\nCommands functions list:')
    for j in range(num_commands):
        file.write("\n" + commands_list_functions[j])
    file.write('\n\nCommands display list:')
    for k in range(num_commands):
        file.write("\n" + commandlist.get(k).rstrip())
    file.close()
    print("save operation")


# LOAD FUNCTIONS #
def load_config():
    global data

    address = askopenfilename(initialdir='modules/configurations')  # opens a dialog to choose file to open
    for j in range(len(modules_list)):
        delete_module(0)  # delete all previous modules
    for k in range(commandlist.size()):
        delete_command(0)  # delete all previous commands
    data = open(address, "r")
    number_of_modules = int(data.readline())  # reads how many modules are to be loaded
    # creates all the modules based on the saved date
    for i in range(number_of_modules):
        newmod = Module(float(data.readline()),
                        float(data.readline()),
                        float(data.readline()),
                        float(data.readline()),
                        float(data.readline()),
                        float(data.readline()),
                        image=str(data.readline()).rstrip(),
                        name=str(data.readline()).rstrip(),
                        mod_rows=int(data.readline()),
                        mod_cols=int(data.readline()))
        newmod.is_dispose = int(data.readline())
        newmod.is_draw = int(data.readline())
        newmod.is_Dispense = int(data.readline())
        newmod.is_GetTip = int(data.readline())
        newmod.is_Print = int(data.readline())
        modules_list.append(newmod)


def load_opera():
    global data
    load_config()
    # skipping to commands functions list
    print(data.readline())
    num_of_commands = int(data.readline().rstrip())
    print(num_of_commands)
    verify = data.readline().rstrip()
    print(verify)
    if verify == 'Commands functions list:':
        for j in range(num_of_commands):
            commands_list_functions.append(data.readline().rstrip())
        # skip lines to next list:
        data.readline()
        data.readline()
        for i in range(num_of_commands):
            commandlist.insert(END, data.readline())
        print('yes')
    else:
        print('no commands saved on file')
    print("loaded operation")


# MANUAL MODE RELATED FUNCTIONS #
def manual_toggle():  # Enabling and disabling manual mode
    # getting access to global variables
    global MANUAL_MODE
    global SHOW_MANUAL_KEY
    MANUAL_MODE = not MANUAL_MODE # switches mode to enable or disable manual mode keys. in 'manual' function
    if MANUAL_MODE:
        # rewrites status bar LED and button state
        manual_toggle.config(relief=SUNKEN)
        manual_led.config(image=gled)
        if SHOW_MANUAL_KEY:
            # pops up a message of keys used in manual mode
            messagebox.showinfo('Manual mode keys', 'Manual mode is activated\nKeys are:\n'+KEY)
            SHOW_MANUAL_KEY = False  # turns of the notification of Keys used for manual mode after one time.
    else:
        # rewrites status bar LED and button state
        manual_toggle.config(relief=RAISED)
        manual_led.config(image=rled)
    root.update()  # updates window


def manual(e):
    if MANUAL_MODE:
        command = e.char
        if command == 'u':
            swift.set_position(z=0.1, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'U':
            swift.set_position(z=3, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100)
        elif command == 'j':
            swift.set_position(z=-0.1, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'J':
            swift.set_position(z=-3, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100)
        elif command == 'm':
            swift.set_position(y=0.2, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'M':
            swift.set_position(y=5, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'n':
            swift.set_position(y=-0.2, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'N':
            swift.set_position(y=-5, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'y':
            swift.set_position(x=-0.2, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'Y':
            swift.set_position(x=-5, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'h':
            swift.set_position(x=0.2, speed=MANUAL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'H':
            swift.set_position(x=5, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'k':
            pipette(10)
        elif command == 'K':
            pipette(50)
        elif command == 'i':
            pipette(-10)
        elif command == 'I':
            pipette(-50)
        elif command == 'p':
            parking()
        elif command == 'Q':
            swift.set_polar(r=-3, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'q':
            swift.set_polar(r=-0.2, speed=MANUAL_ANGEL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'W':
            swift.set_polar(r=3, speed=MANUAL_HIGH_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'w':
            swift.set_polar(r=0.2, speed=MANUAL_ANGEL_LOW_SPEED, relative=True, timeout=100, wait=WAITTF)
        elif command == 'c':
            connect()

# SUB WINDOWS COMMANDS #
class SubWindow:
    def __init__(self, title, icon):
        self.win = Toplevel(root)  # creates a new window
        self.win.resizable(0, 0)   # not scalable
        self.win.geometry("+500+200") # appears at this position on the screen
        self.win.tk.call('wm', 'iconphoto', self.win._w, PhotoImage(file=icon))  # puts the pic on the title
        self.win.title(title)


# PRINT COMMAND #
def show_frame():  # moves the arm to 4 corners of the picture
    if swift.connected:
        num = module_val.get()
        xpos = modules_list[num].originX + float(x_offset_val.get())
        ypos = modules_list[num].originY + float(y_offset_val.get())
        zpos = modules_list[num].surfaceZ + float(z_offset_val.get())
        swift.set_position(x=xpos, y=ypos, z=zpos, speed=AUTO_SPEED * OSM, wait=WAITTF)
        swift.set_position(y=ypos + print_w * modules_list[num].incY, speed=AUTO_SPEED * OSM / 2, wait=WAITTF)
        swift.set_position(x=xpos + print_h * modules_list[num].incX, speed=AUTO_SPEED * OSM / 2, wait=WAITTF)
        swift.set_position(y=ypos, speed=AUTO_SPEED * OSM / 2, wait=WAITTF)
        swift.set_position(x=xpos, speed=AUTO_SPEED * OSM / 2, wait=WAITTF)
    else:
        messagebox.showerror("Error", "Pipettor is not connected, can not show frame", parent=print_window.win)

def load_print_image():
    global print_address
    print_address = askopenfilename(initialdir='test pictures', parent=print_window.win)
    preview_image(threshold.get())


def preview_image(threshold):
    global print_address
    global preview_canvas
    global preview_photoImage
    global print_w
    global print_h
    image = Image.open(print_address)
    image_gs = image.convert('L')
    image_bw = image_gs.point(lambda x: 0 if x < int(threshold)  else 255, '1')
    image_bw.save('preview.png')
    print_w, print_h = image_bw.size
    print(print_h)
    print(print_w)
    preview_canvas.config(width=print_w, height=print_h)
    preview_photoImage = PhotoImage(file='preview.png')
    preview_canvas.create_image((4, 4), image=preview_photoImage, anchor=NW)

def add_print_cmd():
    global module_val  # variable to store the selected module
    global print_window
    global x_offset_val
    global y_offset_val
    global z_offset_val
    global ul_per_pixel
    global threshold
    global e_draw_val
    global preview_canvas
    global print_address

    # composing the string of command to execute
    command_str = "modules_list[" + str(module_val.get()) +\
                  "].pipette_print('" + str(print_address) + \
                  "', th=" + str(threshold.get()) + \
                  ", ul2px=" + ul_per_pixel.get() +\
                  ", xoffset=" + x_offset_val.get() +\
                  ", yoffset=" + y_offset_val.get() +\
                  ", zoffset=" + z_offset_val.get() +\
                  ')'
    # composing string for commands list
    representation_str = "Print " + print_address + "at " + modules_list[module_val.get()].name
    commandlist.insert(END, representation_str)  # storing the representation string at the displayed commands list
    commands_list_functions.append(command_str)  # storing the command string at the executable commands list
    print_window.win.destroy()  # Closing the command window


def test_pipette_print():
    modules_list[0].pipette_print('test pictures/black 2X50.jpg', th=122, ul2px=2.5)


def check_if_print():
    for f in range(len(modules_list)):
        if modules_list[f].is_Print:
            return True
    return False

def print_win():
    # variables for user inputs
    global module_val  # variable to store the selected module
    global print_window
    global x_offset_val
    global y_offset_val
    global z_offset_val
    global ul_per_pixel
    global threshold
    global e_draw_val
    global preview_canvas
    global print_address
    if check_if_print():
        # creating a window
        print_window = SubWindow('Pipette Print', 'Icons\Image print.png')
        win_label = Label(print_window.win, text="Choose module to picture on: ")
        # assigning the variable to be changeable via the new window
        module_val = IntVar(print_window.win)
        ul_per_pixel = StringVar(print_window.win)
        x_offset_val = StringVar(print_window.win)
        y_offset_val = StringVar(print_window.win)
        z_offset_val = StringVar(print_window.win)
        threshold = IntVar(print_window.win)
        e_draw_val = IntVar(print_window.win)
        # assigning default values
        threshold.set(120)
        x_offset_val.set('0.0')
        y_offset_val.set('0.0')
        z_offset_val.set('0.0')
        ul_per_pixel.set('5.0')
        # Creates options for modules only if it is possible to perform this operation in the module
        option_frame = Frame(print_window.win)
        for i in range(len(modules_list)):
            if modules_list[i].is_Print:
                option = Radiobutton(option_frame, text=modules_list[i].name, variable=module_val, value=i)
                option.pack(anchor=S)
                module_val.set(i)
        X_offset_label = Label(print_window.win, text="X offset:")
        X_offset_entry = Entry(print_window.win, text='caption', textvariable=x_offset_val)
        y_offset_label = Label(print_window.win, text="Y offset:")
        y_offset_entry = Entry(print_window.win, text='caption', textvariable=y_offset_val)
        Z_offset_label = Label(print_window.win, text="Z offset:")
        Z_offset_entry = Entry(print_window.win, text='caption', textvariable=z_offset_val)
        ul_per_pixel_label = Label(print_window.win, text="Microliters per pixel:")
        ul_per_pixel_entry = Entry(print_window.win, text='caption', textvariable=ul_per_pixel)
        trashold_label = Label(print_window.win, text="Brightness:")
        trashold_scale = Scale(print_window.win, from_=0, to=255, variable=threshold, command=preview_image)

        load_pic_label = Label(print_window.win,text="Load picture to print")
        load_pic = Button(print_window.win,
                          image=openIcon,
                          command=load_print_image)  # creating add button

        show_frame_butt = Button(print_window.win, text='Show picture frame',
                             command=show_frame,
                             bg='#6BEEFF',
                             activebackground='#6BEEFF')  # creating add button

        add_command = Button(print_window.win, text='Add command to list',
                             command=add_print_cmd,
                             bg='#2ECC71',
                             activebackground='#2ECC71')  # creating add button

        preview_canvas = Canvas(print_window.win, width=200, height=200, relief=RAISED, borderwidth=4)

        e_option_label = Label(print_window.win, text="Choose module for emergency draw: ")
        # Creates options for modules only if it is possible to perform this operation in the module
        e_option_frame = Frame(print_window.win)
        for j in range(len(modules_list)):
            if modules_list[j].is_draw:
                option = Radiobutton(e_option_frame, text=modules_list[j].name, variable=e_draw_val, value=j)
                option.pack(anchor=S)
                e_draw_val.set(j)

        win_label.grid(row=0, columnspan=4)
        option_frame.grid(row=1, columnspan=4)
        X_offset_label.grid(row=2, column=3)
        X_offset_entry.grid(row=3, column=3)
        y_offset_label.grid(row=4, column=3)
        y_offset_entry.grid(row=5, column=3)
        Z_offset_label.grid(row=6, column=3)
        Z_offset_entry.grid(row=7, column=3)
        ul_per_pixel_label.grid(row=8, column=3)
        ul_per_pixel_entry.grid(row=9, column=3)
        load_pic_label.grid(row=9, columnspan=2)
        load_pic.grid(row=2, columnspan=2, rowspan=7)
        trashold_label.grid(row=2, column=2)
        trashold_scale.grid(row=3, column=2, rowspan=7)
        preview_canvas.grid(row=10, columnspan=4)
        show_frame_butt.grid(row=11, columnspan=4)
        e_option_frame.grid(row=12, columnspan=4)
        add_command.grid(row=13, columnspan=4)

        print_address = 'test pictures/Image print 60X60.jpg' # default picture to print
        preview_image(threshold.get())  # preview default picture
    else:
        messagebox.showerror("Error", "No module available for this operation")


# ADD MODULE #
def add_mod():
    newmod = Module(float(xcoord.get()),
                    float(ycoord.get()),
                    float(ztravel.get()),
                    float(zsurface.get()),
                    float(xinc.get()),
                    float(yinc.get()),
                    image=mod_image_address,
                    name=mod_name.get().rstrip(),
                    mod_rows=int(rows.get()),
                    mod_cols=int(columns.get()))
    print(mod_image_address)
    newmod.is_dispose = dispose_cb.get()
    newmod.is_draw = draw_cb.get()
    newmod.is_Dispense = dispense_cb.get()
    newmod.is_GetTip = gettip_cb.get()
    newmod.is_Print = printable_cb.get()
    modules_list.append(newmod)
    add_module.win.destroy()


def get_coords_module():
    global xcoord
    global ycoord
    global zsurface
    global ztravel
    if swift.connected:
        arm_pos = swift.get_position()
        xcoord.set(arm_pos[0])
        ycoord.set(arm_pos[1])
        zsurface.set(arm_pos[2])
        ztravel.set(arm_pos[2]+10)
    else:
        messagebox.showerror("Error", "Pipettor is not connected, can not get position", parent=add_module.win)



def load_image():
    global mod_image_address
    mod_image_address = askopenfilename(initialdir='modules/representing images/',parent=add_module.win)


def add_module_win():  # defining window appearance
    global add_module
    # variables for user inputs
    global mod_name
    global dispose_cb
    global draw_cb
    global dispense_cb
    global printable_cb
    global gettip_cb
    global xinc
    global yinc
    global xcoord
    global ycoord
    global ztravel
    global zsurface
    global rows
    global columns
    global mod_image_address
    mod_image_address = 'modules/representing images/Point.png'
    mod_name = StringVar()
    mod_name.set('New Module')
    dispose_cb = IntVar()
    draw_cb = IntVar()
    dispense_cb = IntVar()
    printable_cb = IntVar()
    gettip_cb = IntVar()
    xinc = StringVar()
    xinc.set('1.0')
    yinc = StringVar()
    yinc.set('1.0')
    xcoord = StringVar()
    xcoord.set('150.0')
    ycoord = StringVar()
    ycoord.set('150.0')
    ztravel = StringVar()
    ztravel.set('0.0')
    zsurface = StringVar()
    zsurface.set('0.0')
    rows = StringVar()
    rows.set('1')
    columns = StringVar()
    columns.set('1')

    #creating a window
    add_module = SubWindow('Add New Module','Icons\AddModule.png')
    nameentry_label = Label(add_module.win, text="Module Name:")
    nameentry = Entry(add_module.win, text='caption', textvariable=mod_name)
    nameentry.grid(row=1, columnspan=4)
    nameentry_label.grid(row=0, columnspan=4)

    # Check boxes section
    usage_label = Label(add_module.win, text="This Module Is Used For: ")
    usage_label.grid(row=2,  columnspan=2, sticky=W)
    Checkbutton(add_module.win, text="Pipette Tips Disposal", variable=dispose_cb).grid(row=3, columnspan=2,  sticky=W)
    Checkbutton(add_module.win, text="Draw Liquids From", variable=draw_cb).grid(row=4, columnspan=2,  sticky=W)
    Checkbutton(add_module.win, text="Dispense liquids Into", variable=dispense_cb).grid(row=5, columnspan=2,  sticky=W)
    Checkbutton(add_module.win, text="Printing DroPixle Pictures", variable=printable_cb).grid(row=6, columnspan=2, sticky=W)
    Checkbutton(add_module.win, text="New Pipette Tips", variable=gettip_cb).grid(row=7, columnspan=2,  sticky=W)

    load_butt = Button(add_module.win, image=loadIcon, borderwidth=4, command=load_image)
    load_butt_lable = Label(add_module.win, text="Load Module's Image")
    load_butt.grid(row=3, column=2, rowspan=4, columnspan=2)
    load_butt_lable.grid(row=7, column=2, columnspan=2)

    origin_label = Label(add_module.win, text="Mudule's Origin:")
    origin_label.grid(row=8, columnspan=2,  sticky=W)
    xentry_label = Label(add_module.win, text="X:")
    xentry = Entry(add_module.win, text='caption', textvariable=xcoord)
    xentry.grid(row=9, column=1, sticky=W)
    xentry_label.grid(row=9, sticky=E)
    yentry_label = Label(add_module.win, text="Y:")
    yentry = Entry(add_module.win, text='caption', textvariable=ycoord)
    yentry.grid(row=10, column=1, sticky=W)
    yentry_label.grid(row=10, sticky=E)
    ztravel_label = Label(add_module.win, text="Travel Z:")
    ztravel_entry = Entry(add_module.win, text='caption', textvariable=ztravel)
    ztravel_entry.grid(row=11, column=1, sticky=W)
    ztravel_label.grid(row=11, sticky=E)
    rows_label = Label(add_module.win, text="Rows:")
    rows_entry = Entry(add_module.win, text='caption', textvariable=rows)
    rows_entry.grid(row=12, column=1, sticky=W)
    rows_label.grid(row=12, column=0, sticky=E)
    xinc_label = Label(add_module.win, text="X increments:")
    xinc_label.grid(row=9, column=2, sticky=W)
    xinc_entry = Entry(add_module.win, text='caption', textvariable=xinc)
    xinc_entry.grid(row=9, column=3, sticky=W)
    yinc_label = Label(add_module.win, text="Y increments:")
    yinc_label.grid(row=10, column=2, sticky=W)
    yinc_entry = Entry(add_module.win, text='caption', textvariable=yinc)
    yinc_entry.grid(row=10, column=3, sticky=W)
    zsurface_label = Label(add_module.win, text="Action Z:")
    zsurface_entry = Entry(add_module.win, text='caption', textvariable=zsurface)
    zsurface_entry.grid(row=11, column=3, sticky=W)
    zsurface_label.grid(row=11, column=2, sticky=E)
    columns_label = Label(add_module.win, text="Columns:")
    columns_entry = Entry(add_module.win, text='caption', textvariable=columns)
    columns_entry.grid(row=12, column=3, sticky=W)
    columns_label.grid(row=12, column=2, sticky=E)
    get_coords_butt = Button(add_module.win, text='Get current coordinates', command=get_coords_module)
    get_coords_butt.grid(row=13, columnspan=4)
    add_module_butt = Button(add_module.win, text='Add Module to Workspace', command=add_mod, bg='#2ECC71')
    add_module_butt.grid(row=14, columnspan=4)

# GET TIP #
def add_cmd_get_tip():
    global module_val  # variable to store the selected module
    global row_val
    global col_val
    global tip_val
    # composing the string of command to execute
    command_str = "modules_list[" + str(module_val.get()) + "].get_tip(" + str(tip_val.get()) + ", row="\
                  + row_val.get() + ', col=' + col_val.get() +')'
    # composing string for commands list
    location_str = "(Row " + row_val.get() + ", Col " + col_val.get() + ")"
    representation_str = "Get " + tip_list[tip_val.get()].model + " Tip From " +\
                         modules_list[module_val.get()].name + location_str
    commandlist.insert(END, representation_str)  # storing the representation string at the displayed commands list
    commands_list_functions.append(command_str)  # storing the command string at the executable commands list
    get_tip_window.win.destroy()  # Closing the command window


def check_if_get_tip():
    for f in range(len(modules_list)):
        if modules_list[f].is_GetTip:
            return True
    return False


def get_tip_win():  # defining window appearance
    # variables for user inputs
    global module_val  # variable to store the selected module
    global get_tip_window
    global tip_val
    global row_val
    global col_val
    if check_if_get_tip():
        # creating a window
        get_tip_window = SubWindow('Get New Pipette Tip', 'Icons\Tip.png')
        win_label = Label(get_tip_window.win, text="Choose module to get new tip from: ")
        win_label.pack()
        # assigning the variable to be changeable via the new window
        module_val = IntVar(get_tip_window.win)
        tip_val = IntVar(get_tip_window.win)
        row_val = StringVar(get_tip_window.win)
        col_val = StringVar(get_tip_window.win)
        # assigning default values
        tip_val.set(CURRENT_TIP)
        row_val.set('0')
        col_val.set('0')
        # Creates options for modules only if it is possible to perform this operation in the module
        for i in range(len(modules_list)):
            if modules_list[i].is_GetTip:
                option = Radiobutton(get_tip_window.win, text=modules_list[i].name, variable=module_val, value=i)
                option.pack(anchor=S)

        row_label = Label(get_tip_window.win, text="Row:")
        row_entry = Entry(get_tip_window.win, text='caption', textvariable=row_val)
        col_label = Label(get_tip_window.win, text="Column:")
        col_entry = Entry(get_tip_window.win, text='caption', textvariable=col_val)
        row_label.pack()
        row_entry.pack()
        col_label.pack()
        col_entry.pack()

        tip_model_label = Label(get_tip_window.win, text="Choose tip model: ")
        tip_model_label.pack()
        # Creates options for all tips models
        for j in range(len(tip_list)):
            option = Radiobutton(get_tip_window.win, text=tip_list[j].model, variable=tip_val, value=j)
            option.pack(anchor=S)
        add_command = Button(get_tip_window.win,
                             text='Add command to list',
                             command=add_cmd_get_tip,
                             bg='#2ECC71',
                             activebackground='#2ECC71')  # creating add button
        add_command.pack(anchor=S)  # placing add button at the bottom
    else:
        messagebox.showerror("Error", "No module available for this operation")



# DISPOSE COMMAND #
def add_cmd_dispose():
    global module_val  # variable to store the selected module
    command_str= "modules_list[" + str(module_val.get()) + '].dispose()'  # composing the string of command to execute
    representation_str = "Dispose Tip at " + modules_list[module_val.get()].name  # composing string for commands list
    commandlist.insert(END, representation_str)  # storing the representation string at the displayed commands list
    commands_list_functions.append(command_str)  # storing the command string at the executable commands list
    disposeW.win.destroy()  # Closing the command window


def check_if_dispose():
    for f in range(len(modules_list)):
        if modules_list[f].is_dispose:
            return True
    return False


def dispose_win():  # dispose tip definition window
    global module_val  # variable to store the selected module
    global disposeW  # variable to store new sub window for closing in "add_cmd_dispose" function
    if check_if_dispose():
        disposeW = SubWindow("Dispose Pipette Tip",
                             "Icons\Dispose.png")  # creating new window for command configuration
        module_val = IntVar(disposeW.win)  # assigning the variable to be changeable via the new window
        module_label = Label(disposeW.win, text="Please Choose the Module to Dispose tip at: ")  # creating title
        module_label.pack(anchor=N)  # placing the title on top of the window
        # Creates an options only if it is possible to perform this operation in the module
        for i in range(len(modules_list)):
            if modules_list[i].is_dispose:
                option = Radiobutton(disposeW.win, text=modules_list[i].name, variable=module_val, value=i)
                option.pack(anchor=S)
                module_val.set(i)  # sets module value default to be one of the possibilities
        # creating add button
        add_command = Button(disposeW.win,
                             text='Add command to list',
                             command=add_cmd_dispose,
                             bg='#2ECC71',
                             activebackground='#2ECC71')
        add_command.pack(anchor=S)  # placing add button at the bottom
    else:
        messagebox.showerror("Error", "No module available for this operation")


# DISPENSE COMMAND #
def add_cmd_dispense():
    # composing the string of command to execute
    command_str = "modules_list[" + str(module_val.get()) +\
                  "].dispense(" + str(row_val.get()) +\
                  ", " + str(column_val.get()) +\
                  ", " + str(amount_val.get()) + ')'
    # composing string for commands list
    representation_str = "Dispense " + str(amount_val.get()) +\
                         " microliters at " + modules_list[module_val.get()].name +\
                         '(Row ' + str(row_val.get()) +\
                         ',Column ' + str(column_val.get()) + ')'
    commandlist.insert(END, representation_str)  # storing the representation string at the displayed commands list
    commands_list_functions.append(command_str)  # storing the command string at the executable commands list
    dispenseW.win.destroy()  # Closing the command window


def check_if_dispense():
    for f in range(len(modules_list)):
        if modules_list[f].is_Dispense:
            return True
    return False


def dispense_win(): # dispense liquid definition window
    global module_val  # variable to store the selected module
    global amount_val  # variable to store the desired amount
    global row_val  # variable to store row input
    global column_val  # variable to store column input
    global dispenseW
    if check_if_dispense():
        dispenseW = SubWindow('Dispense Liquids', 'Icons\Dispense.png')
        module_val = IntVar(dispenseW.win)
        module_label = Label(dispenseW.win, text="Module to Dispense liquid to: ")  # creating title
        module_label.pack(anchor=N)
        # Creates an option only if it is possible to perform this operation in the module
        for i in range(len(modules_list)):
            if modules_list[i].is_Dispense:
                option = Radiobutton(dispenseW.win, text=modules_list[i].name, variable=module_val, value=i)
                option.pack(anchor=S)
                module_val.set(i)  # sets default selected value to the first option
        amount_val = StringVar(dispenseW.win)
        row_val = StringVar(dispenseW.win)
        column_val = StringVar(dispenseW.win)
        amount_val.set(0.0)
        row_val.set(0)
        column_val.set(0)

        amount_label = Label(dispenseW.win, text="Amount to Dispense [ul]: ")
        amount_entry = Entry(dispenseW.win, textvariable=amount_val)
        row_label = Label(dispenseW.win, text="Row: ")
        row_entry = Entry(dispenseW.win, textvariable=row_val)
        column_label = Label(dispenseW.win, text="Column: ")
        column_entry = Entry(dispenseW.win, textvariable=column_val)
        add_command = Button(dispenseW.win,
                             text='Add command to list',
                             command=add_cmd_dispense,
                             bg='#2ECC71',
                             activebackground='#2ECC71')  # creating add button
        amount_label.pack(anchor=S)
        amount_entry.pack(anchor=S)
        row_label.pack(anchor=S)
        row_entry.pack(anchor=S)
        column_label.pack(anchor=S)
        column_entry.pack(anchor=S)
        add_command.pack(anchor=S)  # placing add button
    else:
        messagebox.showerror("Error", "No module available for this operation")



# DRAW COMMAND #
def add_cmd_draw():
    # composing the string of command to execute
    command_str = "modules_list[" + str(module_val.get()) + \
                  "].draw(" + str(row_val.get()) + \
                  ", " + str(column_val.get()) + \
                  ", " + str(-float(amount_val.get())) + ')'
    # composing string for commands list
    representation_str = "Draw " + str(amount_val.get()) + \
                         " microliters at " + modules_list[module_val.get()].name + \
                         '(Row ' + str(row_val.get()) + \
                         ',Column ' + str(column_val.get()) + ')'
    commandlist.insert(END, representation_str)  # storing the representation string at the displayed commands list
    commands_list_functions.append(command_str)  # storing the command string at the executable commands list
    drawW.win.destroy()  # Closing the command window


def check_if_draw():
    for f in range(len(modules_list)):
        if modules_list[f].is_draw:
            return True
    return False


def draw_win(): # draw liquid definition window
    global module_val  # variable to store the selected module
    global amount_val  # variable to store the desired amount
    global row_val  # variable to store row input
    global column_val  # variable to store column input
    global drawW

    if check_if_draw():
        drawW = SubWindow('Draw Liquids', 'Icons\Draw.png')
        module_val = IntVar(drawW.win)
        module_label = Label(drawW.win, text="Module to Draw liquid from: ")
        module_label.pack(anchor=N)
        # Creates an option only if it is possible to perform this operation in the module
        for i in range(len(modules_list)):
            if modules_list[i].is_draw:
                option = Radiobutton(drawW.win, text=modules_list[i].name, variable=module_val, value=i)
                option.pack(anchor=S)
                module_val.set(i)  # sets default selected value to the first option
        amount_val = StringVar(drawW.win)
        row_val = StringVar(drawW.win)
        column_val = StringVar(drawW.win)
        amount_val.set(0.0)
        row_val.set(0)
        column_val.set(0)
        amount_label = Label(drawW.win, text="Amount to Dispense [ul]: ")
        amount_entry = Entry(drawW.win, textvariable=amount_val)
        row_label = Label(drawW.win, text="Row: ")
        row_entry = Entry(drawW.win, textvariable=row_val)
        column_label = Label(drawW.win, text="Column: ")
        column_entry = Entry(drawW.win, textvariable=column_val)
        add_command = Button(drawW.win,
                             text='Add command to list',
                             command=add_cmd_draw,
                             bg='#2ECC71',
                             activebackground='#2ECC71')  # creating add button
        amount_label.pack(anchor=S)
        amount_entry.pack(anchor=S)
        row_label.pack(anchor=S)
        row_entry.pack(anchor=S)
        column_label.pack(anchor=S)
        column_entry.pack(anchor=S)
        add_command.pack(anchor=S)  # placing add button
    else:
        messagebox.showerror("Error", "No module available for this operation")


class PipetteTip:
    def __init__(self, model = 'new tip', engagment_step=20, effective_heghit= 0, microliter_mm = 1.0 ):
        self.model = model  # Name of the product as sold
        self.EH = effective_heghit  # height to be added to the Z travel after position calculations
        self.ES = engagment_step  # step down to engage new tip from its surface z
        self.ul2mm = microliter_mm  # ratio to compensate draw or dispense amounts for each tip
        tip_list.append(self)


class Module:
    def __init__(self , x, y, travel_z, surface_z, inc_x, inc_y,
                 image='Icons\TUL.png', name='New module', mod_rows=0, mod_cols=0):

        self.image_address = image
        self.photoImage = PhotoImage(file=image)
        mod_image_id = baseMap.create_image((MAP_Y_ORIGIN - 10 + y * MAP_PIXELS_PER_MM,
                                             MAP_X_ORIGIN - 230 + x * MAP_PIXELS_PER_MM),
                                            image=self.photoImage,
                                            anchor=NW)
        mod_image_title = baseMap.create_text((MAP_Y_ORIGIN - 10 + y * MAP_PIXELS_PER_MM, MAP_X_ORIGIN- 230 + x * MAP_PIXELS_PER_MM),
                                              text=name,
                                              anchor=SW)
        self.imageID = mod_image_id
        self.imageTitle = mod_image_title
        self.name = name
        self.originX = x
        self.originY = y
        self.travelZ = travel_z
        self.surfaceZ = surface_z
        self.incX = inc_x
        self.incY = inc_y
        self.rows = mod_rows
        self.cols = mod_cols
        self.is_dispose = True
        self.is_draw = True
        self.is_Dispense = True
        self.is_GetTip = True
        self.is_Print = True
        modulelist.insert(END, self.name)  # storing the representation string at the displayed commands list



    def dispose(self):
        if self.is_dispose:
            swift.set_position(x=self.originX,
                               y=self.originY,
                               z=self.travelZ + tip_list[CURRENT_TIP].EH,
                               speed=AUTO_SPEED * OSM,
                               wait=WAITTF)
            swift.set_position(x=self.originX, y=self.originY, z=self.travelZ, speed=AUTO_SPEED*OSM , wait=WAITTF)
            swift.send_cmd_sync("G1 F" + str(PIPETTE_AUTO_SPEED) + " E0", timeout=10)
            pipette(50.0)
            pipette(-50.0)
        else:
            print('can not dispose tip here')

    def draw(self, row=0, col=0, amount=0):
        new_x = self.originX + row * self.incX
        new_y = self.originY + col * self.incY
        new_amount =  amount * tip_list[CURRENT_TIP].ul2mm
        if self.is_draw:
            swift.set_position(x=new_x , y=new_y , z=self.travelZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
            swift.set_position(z=self.surfaceZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
            pipette(new_amount)
            swift.set_position(z=self.travelZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
        else:
            print('can not draw liquid here')

    def dispense(self, row=0, col=0, amount=0):
        new_x = self.originX + row * self.incX
        new_y = self.originY + col * self.incY
        new_amount = amount * tip_list[CURRENT_TIP].ul2mm
        if self.is_Dispense:
            swift.set_position(x=new_x , y=new_y , z=self.travelZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
            swift.set_position(z=self.surfaceZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
            pipette(new_amount)
            swift.set_position(z=self.travelZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
        else:
            print('can not dispense liquid here')

    def get_tip(self, tip, row=0, col=0):
        global CURRENT_TIP
        new_x = self.originX + row * self.incX
        new_y = self.originY + col * self.incY
        swift.set_position(x=new_x, y=new_y, z=self.travelZ, speed=AUTO_SPEED*OSM, wait=WAITTF)
        swift.set_position(x=new_x, y=new_y, z=self.surfaceZ, speed=GET_LOW_SPEED*OSM, wait=WAITTF)
        CURRENT_TIP = tip
        swift.set_position(x=new_x, y=new_y, z=self.travelZ+tip_list[CURRENT_TIP].EH, speed=AUTO_SPEED*OSM, wait=WAITTF)


    def pipette_print(self, file, th=122, ul2px=20.0, xoffset=0, yoffset=0, zoffset=0):
        global p_pos
        if self.is_Print:
            image = Image.open(file)
            image_gs = image.convert('L')
            image_bw = image_gs.point(lambda x: 0 if x < th else 255, '1')
            true_false_araay = np.asarray(image_bw).copy()
            rows, cols = true_false_araay.shape
            image_bw.save('result.png')
            # offset module origin for ofsseted print plane
            self.surfaceZ += zoffset
            self.originX += xoffset
            self.originY += yoffset
            for i in range(rows):
                for j in range (cols):
                    if not(true_false_araay[i][j]):
                        if p_pos >= -50:  #  checking if there is still liquid
                             modules_list[E_DRAW_VAL].draw(0, 0, E_DRAW_AMOUNT)
                        self.dispense(i, j, ul2px)
            # restore module origin
            self.surfaceZ -= zoffset
            self.originX -= xoffset
            self.originY -= yoffset
        else:
            print('can not Print picture here')


def list_up():
    selection = commandlist.curselection()
    index = selection[0]
    if index == 0:
        return
    else:
        text = commandlist.get(index)
        command = commands_list_functions[index]

        commandlist.delete(index)
        commandlist.insert(index-1, text)
        commandlist.selection_set(index - 1)

        del commands_list_functions[index]
        commands_list_functions.insert(index-1, command)


def list_down():
    selection = commandlist.curselection()
    index = selection[0]
    if index == commandlist.size()-1:
        return
    else:
        text = commandlist.get(index)
        command = commands_list_functions[index]
        commandlist.delete(index)
        commandlist.insert(index + 1, text)
        commandlist.selection_set(index + 1)

        del commands_list_functions[index]
        commands_list_functions.insert(index + 1, command)



def run_selected():
    selection = commandlist.curselection()
    print(selection[0])
    exec(commands_list_functions[selection[0]])


def run_list():
    for i in range(commandlist.size()):
        commandlist.selection_set(i)
        if i>0:
            commandlist.selection_clear(i-1)
        root.update()
        exec(commands_list_functions[i])
    if CLEAR_AFTER_RUN:
        for j in range(commandlist.size()):
            commandlist.delete(j)
            del commands_list_functions[j]


def delete_command(index):  # deletes command from both lists
    commandlist.delete(index)
    del commands_list_functions[index]


def delete():
    to_delete = commandlist.curselection()
    for i in range(len(to_delete)):
        commandlist.delete(to_delete[i])
        del commands_list_functions[to_delete[i]]


def clicked():
    print("button clicked")


def pipette(microliters, speed = PIPETTE_AUTO_SPEED):
    global p_pos
    if p_pos + microliters <=50:
        swift.send_cmd_sync("G91")
        swift.send_cmd_sync("G1 F" + str(speed) + " E"+str(microliters), timeout=1000)
        swift.send_cmd_sync("G90")
        p_pos = p_pos + microliters
    else:
        messagebox.showerror("Pipetting Error", "The pipette is empty, Cannot perform the request")
        print("Pipette Is Empty")


def enable_pipette():
    # Turns of Cold Extrusion protection, should return "{OK}" massage
    print_sb("Enabling Pipette...")
    answer = swift.send_cmd_sync("M302 S0")
    if answer == "['OK']":
        print_sb("Pipetting enabled")


def print_sb(message):  #Print in statusbar
    print(message)
    statusbar.config(text=message)
    root.update()


def home_pipette():
    global p_pos
    print_sb("Homing Pipette...")
    while not(swift.get_digital(45)):
        pipette(5)
    while swift.get_digital(45):
        pipette(-1)
    pipette(15)  # Brings the clamp to the correct position to take a new tip
    swift.send_cmd_sync("G92 E0")  # Sets the E stepper current position to 0
    p_pos = 0 # Sets E stepper position tracking variable to 0
    print_sb("Homing done")


class ToolbarButtons:

    def __init__(self, master, photoimage, titlebutt="new button", command=clicked, color='#ffe380'):
        self.frame = Frame(master,width=60,height=80)
        self.frame.pack(side=LEFT)

        self.button = Button(self.frame,
                             image=photoimage,
                             width=60, height=60,
                             command=command,
                             borderwidth=3,
                             bg=color,
                             activebackground=color,
                             state=NORMAL)
        self.button.pack(side=TOP)

        self.label = Label(self.frame, text= titlebutt)
        self.label.pack(side=BOTTOM)


def update_status():
    global arm_pos
    global p_pos
    global status_update_after_id
    #  calling the status update function to run every short time and storing an identifier for later stopping
    status_update_after_id = root.after(100, update_status)
    if swift.connected:
        connect_led.config(image=gled)
        arm_pos = swift.get_position()
        status="Connected | Current position: X=" + str(arm_pos[0]) +" Y=" + str(arm_pos[1])+" Z=" \
               + str(arm_pos[2])+" P: " +str(p_pos)
        statusbar.config(text=status)
        root.update()
    else:
        connect_led.config(image=rled)
        print_sb('TUL Pipettor is not connected')
        root.update()
        root.after_cancel(status_update_after_id)


def parking():
    swift.set_position(z=100, timeout=100, relative=True, wait=WAITTF, speed=AUTO_SPEED*OSM)  # going up to clear route before parking
    swift.set_position(x=105, y=0, z=40, timeout=100, wait=WAITTF, speed=AUTO_SPEED*OSM)  # going to parking position
    swift.flush_cmd(wait_stop=True)


def connect():
    swift.connect()
    connect_led.config(image=yled)
    print_sb('Connecting to Pipettor...')
    swift.waiting_ready(timeout=1000)
    print_sb('Connected')
    device_info = swift.get_device_info()
    firmware_version = device_info['firmware_version']
    if firmware_version and not firmware_version.startswith(('0.', '1.', '2.', '3.')):
        swift.set_speed_factor(0.0005)
    swift.set_mode(0)
    print_sb('Resetting position...')
    swift.reset(wait=WAITTF, speed=AUTO_SPEED*OSM, timeout=1000)  # Resets the uArm to position (200,0,150)
    enable_pipette()   # Enable "cold extrusion"
    home_pipette()  # Moving piston clamp to 0 position and set it to 0
    swift.flush_cmd(wait_stop=True)

    # status bar updates
    update_status()  # makes the status bar position display update start to run

    # changing to connect button state
    connectButt.label.config(text='Disconnect')
    connectButt.button.config(image=disconnectIcon, command=disconnect, bg='#ff9494', activebackground='#ff9494')

    # enabling all buttons after connection done
    manual_toggle.config(state=NORMAL)
    addModuleButt.button.config(state=NORMAL)
    delCommandButt.button.config(state=NORMAL)
    parkButt.button.config(state=NORMAL)
    pipettePrintButt.button.config(state=NORMAL)
    runCommandButt.button.config(state=NORMAL)
    runSelectedCommandButt.button.config(state=NORMAL)
    pipetteDrawButt.button.config(state=NORMAL)
    pipetteDispenseButt.button.config(state=NORMAL)
    disposeButt.button.config(state=NORMAL)
    getTipButt.button.config(state=NORMAL)
    pipetteZeroHomeButt.button.config(state=NORMAL)


def disconnect():
    root.after_cancel(status_update_after_id)
    connect_led.config(image=yled)
    print_sb('Parking Pipettor before disconnecting...')
    parking()
    swift.send_cmd_sync("M18")  # Disabling all motors before disconnecting
    swift.disconnect()  # disconnecting
    # updating status bar
    connect_led.config(image=rled)
    print_sb('TUL Pipettor is not connected')
    # changing to connect button state
    connectButt.label.config(text='Connect')
    connectButt.button.config(image=connectIcon, command=connect, bg='#abffb7', activebackground='#abffb7')


def destroy():
    if swift.connected:
        disconnect()
    root.destroy()


# MAIN WINDOW
root = Tk()
root.title('TUL Pippetor GUI')
root.tk.call('wm', 'iconphoto', root._w, PhotoImage(file='Icons\TUL.png'))
#root.state('zoomed') #makes the window open in maximized state


# CREATING TIPS
tip10ul = PipetteTip('CP10', 20, 40, 59.3)  # mm per microliter
tip25ul = PipetteTip('CP25', 20, 60, 33.3)
tip50ul = PipetteTip('CP50', 20, 40, 16.3)
tip100ul = PipetteTip('CP100', 20, 60, 5.6)
tip250ul = PipetteTip('CP250', 20, 60, 3.1)


# LOADING AND STORING ICONS AND IMAGES
loadIcon = PhotoImage(file="Icons/Open.png")
connectIcon = PhotoImage(file="Icons/connect.png")
disconnectIcon = PhotoImage(file="Icons/disconnect.png")
openIcon = PhotoImage(file='Icons/Open.png')
homeIcon = PhotoImage(file='Icons/Homming.png')
tipIcon = PhotoImage(file='Icons/Tip.png')
disposeIcon = PhotoImage(file='Icons/Dispose.png')
drawIcon = PhotoImage(file='Icons/Draw.png')
dispenseIcon = PhotoImage(file='Icons/Dispense.png')
parkingIcon = PhotoImage(file='Icons/Parking.png')
addModuleIcon = PhotoImage(file='Icons/AddModule.png')
editModuleIcon = PhotoImage(file='Icons/EditMod.png')
delModuleIcon = PhotoImage(file='Icons/DeleteMod.png')
delCommandIcon = PhotoImage(file='Icons/delCommand.png')
runCommandIcon = PhotoImage(file='Icons/RunCommand.png')
runSelectedIcon = PhotoImage(file='Icons/RunSelected.png')
upCommandIcon = PhotoImage(file='Icons/upCommand.png')
downCommandIcon = PhotoImage(file='Icons/DownCommand.png')
printIcon = PhotoImage(file='Icons/Image print.png')
gled = PhotoImage(file='Icons/GreenLED.png')
rled = PhotoImage(file='Icons/RedLED.png')
yled = PhotoImage(file='Icons/YellowLED.png')
mapImage = PhotoImage(file='MAP1000.png')  # MAIN MAP

# TOOLBAR BUTTONS
toolbar = Frame(root, height=80, relief=RAISED, borderwidth=4)  # Creating a frame to store all buttons in
toolbar.pack(side=TOP, fill=X)  # Display the frame on the top of the window
# Adding all buttons to tool bar
disposeButt = ToolbarButtons(toolbar, disposeIcon, "Dispose", dispose_win)
pipetteDispenseButt = ToolbarButtons(toolbar, dispenseIcon, "Dispense", dispense_win)
pipetteDrawButt = ToolbarButtons(toolbar, drawIcon, "Draw", draw_win)
getTipButt = ToolbarButtons(toolbar, tipIcon, "New Tip", get_tip_win)
pipettePrintButt = ToolbarButtons(toolbar, printIcon, "Pipette print", print_win)
pipetteZeroHomeButt = ToolbarButtons(toolbar, homeIcon, "Home", home_pipette)
parkButt = ToolbarButtons(toolbar, parkingIcon, "Parking", parking)
connectButt = ToolbarButtons(toolbar, connectIcon, "Connect", connect, color='#abffb7')
# setting locations of display for all buttons in toolbar
connectButt.frame.pack(side=RIGHT, expand=TRUE)
parkButt.frame.pack(side=RIGHT)
pipetteZeroHomeButt.frame.pack(side=RIGHT)
# enabled buttons on startup, all buttons that are used for connected mode will turn on after connection, see 'connect'
connectButt.button.config(state=NORMAL)

# STATUS BAR
statusframe = Frame(root, borderwidth=2,  relief=SUNKEN)
connect_led = Label(statusframe, image=rled)
connect_led.pack(side=LEFT)
statusbar = Label(statusframe, text='TUL Pipettor is not connected', anchor=W)
statusbar.pack(side=LEFT)
manual_toggle = Button(statusframe,
                       text='Manual Mode',
                       command=manual_toggle,
                       state=DISABLED,
                       font=('TkDefaultFont', '10'),
                       borderwidth=3,
                       bg='#ffc800',
                       activebackground='#ffc800')
manual_toggle.pack(side=RIGHT)
manual_led = Label(statusframe, image=rled)
manual_led.pack(side=RIGHT)
statusframe.pack(side=BOTTOM, fill=X)


# MENU BAR
menubar = Menu(root)


# File pulldown menu
filemenu = Menu(menubar, tearoff=0)
filemenu.add_separator()
filemenu.add_command(label="Load Configuration", command=load_config)
filemenu.add_command(label="Save Configuration", command=save_config)
filemenu.add_separator()
filemenu.add_command(label="Load Operation", command=load_opera)
filemenu.add_command(label="Save Operation", command=save_opera)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=destroy)
menubar.add_cascade(label="File", menu=filemenu)

# help pulldown menu
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="About", command=test_pipette_print)
menubar.add_cascade(label="Help", menu=helpmenu)

# display the menu
root.config(menu=menubar)

# COMMAND LIST SECTION
commandFrame = Frame(root, relief=RAISED, borderwidth=6)
commandFrame.pack(side=LEFT, fill=Y)
speed_bar = Scale(commandFrame,
                  from_=1,
                  to=100,
                  orient=HORIZONTAL,
                  label='SPEED:',
                  length=250,
                  command=set_speed_mul)
speed_bar.set(50)
speed_bar.pack(side=BOTTOM)
commandButtonFrame = Frame(commandFrame)
commandButtonFrame.pack(side=BOTTOM, fill=X)
delCommandButt = ToolbarButtons(commandButtonFrame, delCommandIcon, "Delete", delete, color='#ff9494')
delCommandButt.frame.pack(side=RIGHT)
upCommandButt = ToolbarButtons(commandButtonFrame, upCommandIcon, "Move Up", list_up, color='#e3b3ff')
downCommandButt = ToolbarButtons(commandButtonFrame, downCommandIcon, "Move Down", list_down, color='#e3b3ff')
runSelectedCommandButt = ToolbarButtons(commandButtonFrame, runSelectedIcon, "Run marked", run_selected, color='#e3b3ff')
runCommandButt = ToolbarButtons(commandButtonFrame, runCommandIcon, "Run list", run_list, color='#e3b3ff')
commandListLabel = Label(commandFrame, text='Commands List', font=('TkDefaultFont', '14'), relief=SUNKEN, borderwidth=2)
commandListLabel.pack(side=TOP, fill=X)
scroller = Scrollbar(commandFrame)
scroller.pack(side=RIGHT, fill=Y)
commandlist = Listbox(commandFrame, width=55)
commandlist.pack(side=LEFT, fill=Y)
scroller.config( command = commandlist.yview )

# MODULES LIST
moduleFrame = Frame(root, relief=RAISED, borderwidth=6)
moduleFrame.pack(side=RIGHT, fill=Y)
moduleButtonFrame = Frame(moduleFrame)
moduleButtonFrame.pack(side=BOTTOM, fill=X)
addModuleButt = ToolbarButtons(moduleButtonFrame, addModuleIcon, "Add", add_module_win, color='#a3dcff')
EditModuleButt = ToolbarButtons(moduleButtonFrame, editModuleIcon, "Edit", add_module_win, color='#a3dcff')
delModuleButt = ToolbarButtons(moduleButtonFrame, delModuleIcon, "Delete", delete_module_selected, color='#ff9494')
delModuleButt.frame.pack(side=RIGHT)
moduleListLabel = Label(moduleFrame, text='Modules List', font=('TkDefaultFont', '14'), relief=SUNKEN, borderwidth=2)
moduleListLabel.pack(side=TOP, fill=X)
moduleScroller = Scrollbar(moduleFrame)
moduleScroller.pack(side=RIGHT, fill=Y)
modulelist = Listbox(moduleFrame, width=40)
modulelist.pack(side=LEFT, fill=Y)
moduleScroller.config(command= modulelist.yview )


# MAP #
baseMap = Canvas(root, width=750, height=524, relief=RAISED, borderwidth=4)
baseMap.create_image((MAP_Y_ORIGIN,MAP_X_ORIGIN),image=mapImage)
baseMap.pack(side=LEFT)


# MAIN WINDOW #
root.geometry("+50+50")  # locates the main window on screen
root.bind("<Key>", manual)  # starts listening to keyboard events to activate shortcuts for manual robot operation
root.resizable(0, 0)  # makes the window not resizable by limiting 0 x and 0 y resizing
root.protocol("WM_DELETE_WINDOW", destroy)  # defining callback in case of closing the main window
root.mainloop()  # runs the main window display loop
