# =============================================================================
# Authors: PAR Government
# Organization: DARPA
#
# Copyright (c) 2016 PAR Government
# All rights reserved.
#==============================================================================


import ttk
import tkMessageBox
from maskgen.group_filter import GroupFilterLoader
import  tkFileDialog, tkSimpleDialog
from PIL import ImageTk
from maskgen.ui.autocomplete_it import AutocompleteEntryInText
from maskgen.tool_set import imageResize, imageResizeRelative, fixTransparency, openImage, openFile, validateTimeString, \
    validateCoordinates, getMaskFileTypes, getImageFileTypes, videofiletypes, coordsFromString, IntObject, get_icon,convertToVideo
from maskgen.scenario_model import Modification,ImageProjectModel
from maskgen.services.probes import CompositeExtender
from maskgen.video_tools import get_start_frame_from_segment, get_start_time_from_segment, get_file_from_segment,\
    get_end_frame_from_segment, FileMetaDataLocator
from maskgen.support import getValue
import numpy as np
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.patches as mpatches
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from tkintertable import TableCanvas, TableModel
from maskgen.image_wrap import ImageWrapper
from functools import partial
from maskgen.group_filter import GroupOperationsLoader
from maskgen.ui.PictureEditor import PictureEditor
from maskgen.ui.CompositeViewer import  ScrollCompositeViewer
from maskgen.validation.core import ValidationMessage,Severity
from maskgen.ui.semantic_frame import *
from maskgen.ui.ui_tools import SelectDialog, EntryDialog, TimeWidget


def resolve_argument_type(arg_type, source_type):
    """
    Alter type based on source file type
    :param arg_type:
    :param source_type:
    :return:
    """
    if arg_type == "frame_or_time":
        if source_type == "audio":
           return "time"
        else:
            return "int[0:1000000000]"  # Frame Number
    return arg_type

def checkMandatory(grpLoader, operationName, sourcefiletype, targetfiletype, argvalues):
    """

    :param grpLoader:
    :param operationName:
    :param sourcefiletype:
    :param targetfiletype:
    :param argvalues:
    :return:
    @type grpLoader: GroupOperationsLoader
    """
    ok = True
    op = grpLoader.getOperationWithGroups(operationName,fake=True)
    for k, v in op.mandatoryparameters.iteritems():
        if 'source' in v and v['source'] != sourcefiletype:
            continue
        if 'target' in v and v['target'] != targetfiletype:
            continue
        ok &= (k in argvalues and argvalues[k] is not None and len(str(argvalues[k])) > 0)
    for k, v in op.optionalparameters.iteritems():
        if 'source' in v and v['source'] != sourcefiletype:
            continue
        if 'target' in v and v['target'] != targetfiletype:
            continue
        if 'rule' in v and len(v['rule']) > 0:
            nomatches = [rk for rk, rv in v['rule'].iteritems() if rk in argvalues and argvalues[rk] not in rv]
            ok &= (len(nomatches) > 0 or
                   (k in argvalues and argvalues[k] is not None and len(str(argvalues[k])) > 0))
    if op.parameter_dependencies is not None:
        for param_name, param_requirements in op.parameter_dependencies.iteritems():
            if param_name in argvalues and argvalues[param_name] in param_requirements:
                checks = param_requirements[argvalues[param_name]]
                if type(checks) in [str, unicode]:
                    checks = [checks]
                for check_name in checks:
                    ok &= (check_name in argvalues and argvalues[check_name] is not None and len(str(argvalues[check_name])) > 0)
    return ok

def checkValue(name, value_type, value):
    """
    Check the value given the type
    :param name:
    :param type:
    :param value:
    :return: None,error message if invalid or value,None if valid
    @type name: str
    @type value: str
    @rtype: (str,str)
    """
    if value is not None and (type(value) != 'str' or len(value) > 0):
        if value_type.startswith('float'):
            try:
                vals = [float(x) for x in value_type[value_type.rfind('[') + 1:-1].split(':')]
            except ValueError:
                vals = [-sys.float_info.max, sys.float_info.max]
            try:
                value = float(value)
                if value < vals[0] or value > vals[1]:
                    raise ValueError(value)
            except:
                return None, 'Invalid value for ' + name + '; not in range ' + str(vals[0]) + ' to ' + str(
                    vals[1])
        elif value_type.startswith('int'):
            try:
                if value == '':
                    value = 0
                _match = re.search(r"\[(.*?)\]", value_type).group(1)
                vals = [int(x) for x in _match.split(':')]
            except ValueError:
                vals = [-sys.maxint, sys.maxint]
            try:
                value = int(value)
                if value < vals[0] or value > vals[1]:
                    raise ValueError(value)
            except:
                return None, 'Invalid value for ' + name + '; not in range ' + str(vals[0]) + ' to ' + str(
                    vals[1])
        elif value_type == 'time':
            if not validateTimeString(value):
                return None, 'Invalid time value for ' + name + '; not in format 00:00:00.000000 for time or 0 for frame number'
        elif value_type == 'coordinates':
            if not validateCoordinates(value):
                return None, 'Invalid coordinate value for ' + name + '; not (0,0) format'
    return value, None


def fillTextVariable(obj, row, event):
    """
    Call back for a StringVar or Text Widget object
    get the object's text widget at row and pull the text
    :param obj: PropertyFrame widgets
    :param row:  row number of the widet
    :param event: not used
    :return:
    @type obj: PropertyFrame
    @type row: int
    """
    obj.values[row].set(obj.widgets[row].get(1.0, END))


def promptForURLAndFillButtonText(obj, id, row):
    """
    Prompt for a URL.
    Set the button's text, identify by the id.
    :param obj:
    :param dir: Starting place for file inspection
    :param id: button identitifer
    :param row:
    :param filetypes:
    @type obj: PropertyFrame
    @type row: int
    @type filetypes: [(str,str)]
    :return:
    """
    var = obj.values[row]
    val = URLCaptureDialog(obj, var.get().split('\n'))
    url_string = '\n'.join(val.urls).strip()
    var.set(url_string)
    items = [re.sub('^http[s]*://', '', item) for item in val.urls]
    obj.buttons[id].configure(text=' '.join(items)[:40],
                               width=len(' '.join(items)),
                               height =(len(val.urls))+1)

def promptForFileAndFillButtonText(obj, dir, id, row, filetypes):
    """
    Prompt for a file given the file types.
    Set the button's text, identify by the id.
    :param obj:
    :param dir: Starting place for file inspection
    :param id: button identitifer
    :param row:
    :param filetypes:
    @type obj: PropertyFrame
    @type row: int
    @type filetypes: [(str,str)]
    :return:
    """
    if '*.*' in [t[1] for t in filetypes]:
        filetypes = []
    val = tkFileDialog.askopenfilename(initialdir=dir, title="Select " + id,
                                       filetypes=filetypes)
    var = obj.values[row]
    if val is not None and len(val) > 0:
        var.set(val)
    else:
        var.set('')
        val = None
    obj.buttons[id].configure(text=os.path.split(val)[1] if (val is not None and len(val) > 0) else ' ' * 30)


def promptForFolderAndFillButtonText(obj, dir, id, row):
    """
    Prompt for a folder given the file types.
    Set the button's text, identify by the id.
    :param obj:
    :param dir: Starting place for browser
    :param id: button identitifer
    :param row:
    @type obj: PropertyFrame
    @type row: int
    :return:
    """
    val = tkFileDialog.askdirectory(initialdir=dir, title="Select " + id)
    var = obj.values[row]
    if val is not None and len(val) > 0:
        var.set(val)
    else:
        val = None
    obj.buttons[id].configure(text=os.path.split(val)[1] if (val is not None and len(val) > 0) else ' ' * 30)


def promptForBoxPairAndFillButtonText(obj, id, row):
    """
    Prompt for a donor and place the name in the button text
    Set the variable to the  selected image node name
    :param obj:
    :param id:
    :param var:
    @type obj: PropertyFrame
    @type row: int
    :return:
    """
    extra_args = obj.extra_args
    var = obj.values[row]
    initial_value  = var.get()
    full_value_left = '(0,0,{},{})'.format(extra_args['start_im'].size[0],extra_args['start_im'].size[1])
    full_value_right = '(0,0,{},{})'.format(extra_args['end_im'].size[0], extra_args['end_im'].size[1])
    parts =  initial_value.split(':') if initial_value is not None and len(initial_value) > 0 \
        else [full_value_left,full_value_right,'0']
    left_box = coordsFromString(parts[0])
    right_box = coordsFromString(parts[1])
    angle = int(float(parts[2]))
    d = PointsViewDialog (obj,left_box, right_box,angle,
                          extra_args['start_im'], extra_args['end_im'],
                          extra_args['model'],
                          op=extra_args['op'],
                          argument_name=id)
    if not d.cancelled:
        res = d.getStringConfiguration()
        var.set(res if (res is not None and len(res) > 0) else None)
        obj.buttons[id].configure(text=res if (res is not None and len(res) > 0) else '')

def promptForDonorandFillButtonText(obj, id, row):
    """
    Prompt for a donor and place the name in the button text
    Set the variable to the  selected image node name
    :param obj:
    :param id:
    :param var:
    @type obj: PropertyFrame
    @type row: int
    :return:
    """
    d = ImageNodeCaptureDialog(obj, obj.propertyFunction.scModel)
    res = d.selectedImage
    var = obj.values[row]
    var.set(res if (res is not None and len(res) > 0) else None)
    obj.buttons[id].configure(text=res if (res is not None and len(res) > 0) else '')

def promptForParameter(parent, dir, argumentTuple, filetypes, initialvalue):
    """
     argumentTuple is (name,dict(values, type,descriptipn))
     type is list, imagefile, donor, float, int, time.  float and int have a range in the follow format: [-80:80]

    """
    res = None
    if argumentTuple[1]['type'] == 'file:image':
        val = tkFileDialog.askopenfilename(initialdir=dir, title="Select " + argumentTuple[0], filetypes=filetypes)
        if (val != None and len(val) > 0):
            res = val
    elif argumentTuple[1]['type'].startswith('file:'):
        prop = argumentTuple[1]['type']
        typematch = '*.' + prop[prop.find(':') + 1:]
        typename = prop[prop.find(':') + 1:].upper()
        val = tkFileDialog.askopenfilename(initialdir=dir, title="Select " + argumentTuple[0],
                                           filetypes=[(typename, typematch)])
        if (val != None and len(val) > 0):
            res = val
    elif argumentTuple[1]['type'].startswith('fileset:'):
        initialdir_parts = tuple(argumentTuple[1]['type'][8:].split('/'))
        initialdir = os.path.join(*tuple(initialdir_parts))
        val = tkFileDialog.askopenfilename(initialdir=initialdir, title="Select " + argumentTuple[0],
                                           filetypes=[('Text', '*.txt')])
        if (val != None and len(val) > 0):
            res = val
    elif argumentTuple[1]['type'].startswith('donor'):
        d = ImageNodeCaptureDialog(parent, parent.scModel)
        res = d.selectedImage
    elif argumentTuple[1]['type'].startswith('float'):
        v = argumentTuple[1]['type']
        vals = [float(x) for x in v[v.rfind('[') + 1:-1].split(':')]
        res = tkSimpleDialog.askfloat("Set Parameter " + argumentTuple[0], argumentTuple[1]['description'],
                                      minvalue=vals[0], maxvalue=vals[1],
                                      parent=parent, initialvalue=initialvalue)
    elif argumentTuple[1]['type'].startswith('int'):
        v = argumentTuple[1]['type']
        _match = re.search(r"\[(.*?)\]", v).group(1)
        vals = [int(x) for x in _match.split(':')]
        res = tkSimpleDialog.askinteger("Set Parameter " + argumentTuple[0], argumentTuple[1]['description'],
                                        minvalue=vals[0], maxvalue=vals[1],
                                        parent=parent, initialvalue=initialvalue)
    elif argumentTuple[1]['type'] == 'list':
        d = SelectDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'],
                         argumentTuple[1]['values'])
        res = d.choice
    elif argumentTuple[1]['type'] == 'yesno':
        d = SelectDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'], ['yes', 'no'])
        res = d.choice
    elif argumentTuple[1]['type'] == 'time':
        d = EntryDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'],
                        validateTimeString, initialvalue=initialvalue)
        res = d.choice
    elif argumentTuple[1]['type'] == 'coordinates':
        d = EntryDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'],
                        validateCoordinates, initialvalue=initialvalue)
        res = d.choice
    elif argumentTuple[1]['type'].startswith('listfromfile:'):
        filename = getFileName(argumentTuple[1]['type'][13:])
        if not os.path.isfile(filename):
            d = EntryDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'], None,
                            initialvalue=initialvalue)
            res = d.choice
        else:
            with open(filename) as f:
                values = sorted(f.read().splitlines())
            d = SelectDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'], values)
            res = d.choice
    else:
        d = EntryDialog(parent, "Set Parameter " + argumentTuple[0], argumentTuple[1]['description'], None,
                        initialvalue=initialvalue)
        res = d.choice
    return res


def getCategory(grpLoader,mod):
    """

    :param grpLoader:
    :param mod:
    :return:
     @type grpLoader: GroupOperationsLoader
     @mod mod: Operation
    """
    if mod.category is not None and len(mod.category) > 0:
        return mod.category
    return grpLoader.getCategoryForOperation(mod.operationName)

def tupletostring(tuple):
    strv = ''
    for item in tuple[1:]:
        if type(item) is str:
            strv = strv + item + ' '
        else:
            strv = strv + str(item) + ' '
    return strv


def viewInfo(description_information):
    tkMessageBox.showinfo(description_information[0],
                          description_information[1] if description_information[1] is not None else 'Undefined')


class MyDropDown(OptionMenu):

    var = None
    command = None

    def __init__(self, master, oplist, initialValue=None,command=None):
        if initialValue is None:
            initialValue = oplist[0] if len(oplist) > 0 else ''
        self.var = StringVar()
        self.var.set(initialValue)
        self.command = command
        OptionMenu.__init__(self, master, self.var,'')
        for val in oplist:
            self.children['menu'].add_command(label=val, command=lambda v=self.var, l=val: self.set(l))

    def set_completion_list(self, values, initialValue=None):
        if initialValue is None:
            initialValue = values[0] if len(values) > 0 else ''
        if initialValue not in values:
            initialValue = ''
        self.children['menu'].delete(0, END)
        for val in values:
            self.children['menu'].add_command(label=val, command=lambda v=self.var, l=val: self.set(l))
        self.var.set(initialValue)

    def get(self):
        return self.var.get()

    def set(self,v):
        self.var.set(v)
        if self.command is not None:
            self.command(self.var.get())

    def bind(self, name, command):
        self.command = command

class PropertyFunction:
    """
    Set and Get values for a property for a given name.
    Used by general UI frames and property updaters, regardless of the source of the property (e.g. node, edge, system).
    """

    def getValue(self, name):
        return None

    def setValue(self, name,value):
        return None

class DescriptionCaptureDialog(Toplevel):
    photo = None
    c = None

    def __init__(self, parent, uiProfile, scModel, targetfiletype, end_im, name, description=None):
        """

        :param parent:
        :param uiProfile:
        :param scModel:
        :param targetfiletype:
        :param im:
        :param name:
        :param description:
        @type scModel: ImageProjectModel
        """
        self.im = None
        self.dir = scModel.get_dir()
        self.uiProfile = uiProfile
        self.end_im = end_im
        self.start_im = scModel.startImage()
        self.parent = parent
        self.cancelled = True
        self.argBox = None
        self.scModel = scModel
        self.argvalues = {}
        self.arginfo = []
        self.op = None
        self.inputMaskName = None
        self.sourcefiletype = scModel.getStartType()
        self.targetfiletype = targetfiletype
        self.argvalues = description.arguments if description is not None else {}
        self.description = description if description is not None else Modification('', '')
        self.softwareLoader = SoftwareLoader()
        Toplevel.__init__(self, parent)
        self.withdraw()  # remain invisible for now
        # If the master is not viewable, don't
        # make the child transient, or else it
        # would be opened withdrawn
        if parent.winfo_viewable():
            self.transient(parent)

        self.title(name)
        self.parent = parent
        self.result = None
        body = Frame(self)
        self.initial_focus = self.body(body)
        body.pack_propagate(True)
        self.buttonbox()
        body.pack(padx=5, pady=5)

        if not self.initial_focus:
            self.initial_focus = self

        self.protocol("WM_DELETE_WINDOW", self.cancel)

        if self.parent is not None:
            self.geometry("+%d+%d" % (parent.winfo_rootx() + 50,
                                      parent.winfo_rooty() + 50))

        self.deiconify()  # become visibile now

        self.initial_focus.focus_set()

        # wait for window to appear on screen before calling grab_set
        self.wait_visibility()
        self.grab_set()
        self.wait_window(self)

    def destroy(self):
        '''Destroy the window'''
        self.initial_focus = None
        Toplevel.destroy(self)

    def newsoftware(self, event):
        sname = self.e4.get()
        self.e5.set_completion_list(self.softwareLoader.get_versions(sname,software_type=self.sourcefiletype),
                                    initialValue=self.softwareLoader.get_preferred_version(name=sname))

    def buildArgBox(self, opname):
        if self.argBox is not None:
            self.argBox.destroy()
        for argumentTuple in self.arginfo:

            if argumentTuple[0] not in self.argvalues and \
                'defaultvalue' in argumentTuple[1]:
                self.argvalues[argumentTuple[0]] = argumentTuple[1]['defaultvalue']

        properties = [ProjectProperty(name=argumentTuple[0],
                                      description=argumentTuple[0],
                                      information=argumentTuple[1]['description'] if 'description' in argumentTuple[1] else '',
                                      type=resolve_argument_type(argumentTuple[1]['type'], self.sourcefiletype),
                                      values=self.op.getParameterValuesForType(argumentTuple[0], self.sourcefiletype),
                                      value=self.argvalues[argumentTuple[0]] if argumentTuple[0] in self.argvalues else None) for argumentTuple in self.arginfo]
        self.argBox = PropertyFrame(self.argBoxMaster, properties,
                                propertyFunction=EdgePropertyFunction(properties, self.scModel),
                                changeParameterCB=self.changeParameter,
                                extra_args={'end_im': self.end_im,
                                            'start_im':self.start_im,
                                            'model': self.scModel,
                                            'op': opname},
                                dir=self.dir)
        self.argBox.pack()

    def newcommand(self, event):
        self.op = self.scModel.getGroupOperationLoader().getOperationWithGroups(self.opname.get())
        self.arginfo = []
        if self.op is not None:
            for k, v in self.op.mandatoryparameters.iteritems():
                if 'source' in v and v['source'] != self.sourcefiletype:
                    continue
                if 'target' in v and v['target'] != self.targetfiletype:
                    continue
                self.arginfo.append((k, v))
            for k, v in self.op.optionalparameters.iteritems():
                if 'source' in v and v['source'] != self.sourcefiletype:
                    continue
                if 'target' in v and v['target'] != self.targetfiletype:
                    continue
                self.arginfo.append((k, v))
        self.buildArgBox(self.opname.get())
        if self.okButton is not None:
            self.okButton.config(state=ACTIVE if self.__checkParams() else DISABLED)

    def organizeOperationsByCategory(self):
        return self.scModel.getGroupOperationLoader().getOperationsByCategoryWithGroups(self.sourcefiletype, self.targetfiletype)

    def newcategory(self, event):
        opByCat = self.organizeOperationsByCategory()
        if self.e1.get() in opByCat:
            self.oplist = opByCat[self.e1.get()]
            self.opname.set(self.oplist[0] if self.oplist else "")
            self.newcommand(event)
        else:
            self.oplist = []

    def body(self, master):
        top = Frame(master)
        bottom = Frame(master)
        self.argBoxMaster = bottom
        self.topBody(top)
        self.bottomBody(bottom)
        master.pack_propagate(True)
        top.pack_propagate(True)
        top.pack(padx=5, pady=5)
        bottom.pack(padx=5, pady=5)

    def bottomBody(self, master):
        return self.buildArgBox(self.opname.get())

    def topBody(self, master):
        self.okButton = None

        self.photo = ImageTk.PhotoImage(fixTransparency(imageResize(self.end_im, (250, 250))).toPIL())
        self.c = Canvas(master, width=250, height=250)
        self.c.create_image(125, 125, image=self.photo, tag='imgd')
        self.c.grid(row=0, column=0, columnspan=2)
        Label(master, text="Category:").grid(row=1, sticky=W)
        Label(master, text="Operation:").grid(row=2, sticky=W)
        # self.attachImage = ImageTk.PhotoImage(file="icons/question.png")
        self.b = Button(master, bitmap='info', text="Help", command=self.help, borderwidth=0, relief=FLAT)
        self.b.grid(row=2, column=3)
        Label(master, text="Description:").grid(row=3, sticky=W)
        Label(master, text="Software Name:").grid(row=4, sticky=W)
        Label(master, text="Software Version:").grid(row=5, sticky=W)
        #Label(master, text='Semantic Groups:', anchor=W, justify=LEFT).grid(row=6, column=0)

        self.semanticFrame = SemanticFrame(master)
        self.semanticFrame.grid(row=6,column=0,columnspan=3,sticky=W)
        row = 8
        Label(master, text='Parameters:', anchor=W, justify=LEFT).grid(row=row, column=0, columnspan=2)
        row += 1


        self.opname = StringVar()
        self.opname.set(self.description.operationName)
        cats = self.organizeOperationsByCategory()
        catlist = list(cats.keys())
        catlist.sort()
        self.oplist = cats[catlist[0]] if len(cats) > 0 else []
        self.e1 = MyDropDown(master, catlist, command=self.newcategory)
        self.e2 = Button(master, textvar=self.opname, command=self.select_op)
        self.e4 = MyDropDown(master, sorted(self.softwareLoader.get_names(self.sourcefiletype), key=str.lower), command=self.newsoftware)
        self.e5 = AutocompleteEntryInText(master, values=[], takefocus=False, width=40)
        self.e1.bind("<Return>", self.newcategory)
        self.e1.bind("<<ComboboxSelected>>", self.newcategory)
        self.e2.bind("<Return>", self.select_op)
        self.e4.bind("<Return>", self.newsoftware)
        self.e4.bind("<<ComboboxSelected>>", self.newsoftware)
        self.e3 = Text(master, height=2, width=40, font=('Times', '14'), relief=RAISED, borderwidth=2)

        self.e1.grid(row=1, column=1, sticky=EW)
        self.e2.grid(row=2, column=1, sticky=EW)
        self.e3.grid(row=3, column=1, sticky=EW)
        self.e4.grid(row=4, column=1, sticky=EW)
        self.e5.grid(row=5, column=1)

        if self.description is not None:
            if self.description.semanticGroups is not None:
                pos = 1
                for grp in self.description.getSemanticGroups():
                    self.semanticFrame.insertListbox(pos,grp)
                    pos += 1
            if (self.description.inputMaskName is not None):
                self.inputMaskName = self.description.inputMaskName
            if self.description.operationName is not None and len(self.description.operationName) > 0:
                selectCat = getCategory(self.scModel.getGroupOperationLoader(),self.description)
                self.e1.set_completion_list(catlist, initialValue=selectCat)
                self.oplist = cats[selectCat] if selectCat in cats else []
                # self.e2.set_completion_list(oplist, initialValue=self.description.operationName)
            if (self.description.additionalInfo is not None):
                self.e3.delete(1.0, END)
                self.e3.insert(1.0, self.description.additionalInfo)
            self.newcommand(None)

        if self.description.software is not None:
            self.e4.set_completion_list(sorted(self.softwareLoader.get_names(self.sourcefiletype), key=str.lower),
                                        initialValue=self.description.software.name)
            self.e5.set_completion_list(sorted(self.softwareLoader.get_versions(self.description.software.name,
                                                                                software_type=self.sourcefiletype,
                                                                                version=self.description.software.version)),
                                        initialValue=self.description.software.version)
        else:
            self.e4.set_completion_list(sorted(self.softwareLoader.get_names(self.sourcefiletype), key=str.lower),
                                        initialValue=self.softwareLoader.get_preferred_name())
            self.e5.set_completion_list(
                sorted(self.softwareLoader.get_versions(self.softwareLoader.get_preferred_name(),
                                                        software_type=self.sourcefiletype)),
                initialValue=self.softwareLoader.get_preferred_version(self.softwareLoader.get_preferred_name()))

        return self.e1  # initial focus

    def set_op_name(self,name):
        self.opname.set(name)
        self.newcommand(None)

    def select_op(self):
        if len(self.oplist) > 0:
            SelectDialog(self,
                         "Set Operation",
                         "Select an operation",
                         self.oplist,
                         information="operation",
                         initial_value=self.opname.get(),
                         callback=self.set_op_name)
        else:
            tkMessageBox.showerror("No Operations", "There are no available operations under the current category.")

    def __getinfo(self,name):
        for k,v in self.arginfo:
            if k == name:
                return v
        return None

    def __checkParams(self):
        ok = True
        for k,v in self.argvalues.iteritems():
            info = self.__getinfo(k)
            if info is None:
                continue
            cv,error = checkValue(k,info['type'],v)
            if v is not None and len(str(v)) > 0 and cv is None:
                ok = False
        ok &= checkMandatory(self.scModel.getGroupOperationLoader(),self.opname.get(),self.sourcefiletype,self.targetfiletype,self.argvalues)
        return ok

    def buttonbox(self):
        box = Frame(self)
        self.okButton = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE,
                               state=ACTIVE if self.__checkParams() else DISABLED)
        self.okButton.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Escape>", self.cancel)
        box.pack(side=BOTTOM)

    def ok(self, event=None):

        self.withdraw()
        self.update_idletasks()
        try:
            self.apply()
        finally:
            self.cancel()

    def changeParameter(self, name, type, value):
        self.argvalues[name] = value
        if name == 'inputmaskname' and value is not None:
            self.inputMaskName = value
        if self.okButton is not None:
            self.okButton.config(state=ACTIVE if self.__checkParams() else DISABLED)

    def help(self):
        op = self.scModel.getGroupOperationLoader().getOperationWithGroups(self.opname.get())
        if op is not None:
            tkMessageBox.showinfo(op.name, op.description if op.description is not None and len(
                op.description) > 0 else 'No description')

    def cancel(self):
        if self.cancelled:
            self.description = None
        # put focus back to the parent window
        if self.parent is not None:
            self.parent.focus_set()
        self.destroy()

    def apply(self):
        self.cancelled = False
        self.description.setFromOperation(self.scModel.getGroupOperationLoader().getOperationWithGroups(self.opname.get(),fake=True),
                                          filetype = self.sourcefiletype)
        self.description.setOperationName(self.opname.get())
        self.description.setAdditionalInfo(self.e3.get(1.0, END).strip())
        self.description.setInputMaskName(self.inputMaskName)
        self.description.semanticGroups =  list(self.semanticFrame.getListContents(0, END))
        self.description.setArguments(
            {k: v for (k, v) in self.argvalues.iteritems() if v is not None  and len(str(v)) > 0 and (k in [x[0] for x in self.arginfo])})
        self.description.setSoftware(Software(self.e4.get(), self.e5.get()))
        if (self.softwareLoader.add(self.description.software)):
            self.softwareLoader.save()


class ItemDescriptionCaptureDialog(Toplevel):
    """
    Edit properties of a graph item (node, edge, etc.)
    """

    def __init__(self, parent,  dictionary, properties, name, sourcefiletype=None):
        """

       :param parent: parent frame
       :param uiProfile:
       :param dictionary: items to inspect/edt
       :param properties: descriptionof items
       :param name: title of window
        """
        self.parent = parent
        self.cancelled = True
        self.argvalues = {}
        self.properties = properties
        self.sourcefiletype=sourcefiletype
        for prop_name in self.properties:
            if prop_name in dictionary:
                self.argvalues[prop_name] = dictionary[prop_name]

        Toplevel.__init__(self, parent)
        self.withdraw()  # remain invisible for now
        # If the master is not viewable, don't
        # make the child transient, or else it
        # would be opened withdrawn
        if parent.winfo_viewable():
            self.transient(parent)

        self.title(name)

        self.parent = parent

        self.result = None

        body = Frame(self)
        self.initial_focus = self.body(body)
        self.buttonbox()
        body.pack(padx=5, pady=5, fill=BOTH, expand=True)
        #body.pack_propagate(True)

        if not self.initial_focus:
            self.initial_focus = self

        self.protocol("WM_DELETE_WINDOW", self.cancel)

        if self.parent is not None:
            self.geometry("+%d+%d" % (parent.winfo_rootx() + 50,
                                      parent.winfo_rooty() + 50))

        self.deiconify()  # become visibile now

        self.initial_focus.focus_set()

        # wait for window to appear on screen before calling grab_set
        self.wait_visibility()
        self.grab_set()
        self.wait_window(self)

    def destroy(self):
        '''Destroy the window'''
        self.initial_focus = None
        Toplevel.destroy(self)

    def buildArgBox(self, master):

        disp_properties = [ProjectProperty(name=prop_name,
                                      description=prop_name,
                                      information=prop_def['description'],
                                      type=resolve_argument_type(prop_def['type'],self.sourcefiletype),
                                      values=prop_def['values'] if 'values' in prop_def else [],
                                      value=self.argvalues[prop_name] if prop_name in self.argvalues else None) \
                      for prop_name, prop_def in self.properties.iteritems()]
        argBox= PropertyFrame(master, disp_properties,
                                propertyFunction=NodePropertyFunction(self.argvalues),
                                changeParameterCB=self.changeParameter,
                                dir='.')
        argBox.pack(padx=5, pady=5, fill=BOTH, expand=True)


    def body(self, master):
        self.okButton = None
        Label(master, text='Parameters:', anchor=W, justify=LEFT).pack()
        self.buildArgBox(master)

    def buttonbox(self):
        box = Frame(self)
        self.okButton = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE,
                               state=ACTIVE)
        self.okButton.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Escape>", self.cancel)
        box.pack(side=BOTTOM)

    def ok(self, event=None):
        self.withdraw()
        self.update_idletasks()
        try:
            self.apply()
        finally:
            self.cancel()

    def changeParameter(self, name, type, value):
        self.argvalues[name] = value

    def cancel(self):
        if self.cancelled:
            self.argvalues = None
        # put focus back to the parent window
        if self.parent is not None:
            self.parent.focus_set()
        self.destroy()

    def apply(self):
        self.cancelled = False



class DescriptionViewDialog(tkSimpleDialog.Dialog):
    description = None
    metadiff = None
    metaBox = None


    def __init__(self, parent, scModel, name, description=None, metadiff=None):
        """

        :param parent:
        :param scModel:
        :param im:  end image
        :param name:
        :param description:
        :param metadiff:
        @type scModel: ImageProjectModel
        """
        self.dir = scModel.get_dir()
        self.parent = parent
        self.scModel = scModel
        self.description = description if description is not None else Modification('', '')
        self.metadiff = metadiff
        tkSimpleDialog.Dialog.__init__(self, parent, name)

    def body(self, master):
        vscrollbar = Scrollbar(master, orient=VERTICAL)
        Label(master, text="Operation:", anchor=W, justify=LEFT).grid(row=0, column=0, sticky=W)
        Label(master, text="Description:", anchor=W, justify=LEFT).grid(row=1, column=0, sticky=W)
        Label(master, text="Software:", anchor=W, justify=LEFT).grid(row=2, column=0, sticky=W)
        Label(master, text=getCategory(self.scModel.getGroupOperationLoader(),self.description), anchor=W, justify=LEFT).grid(row=0, column=1, sticky=W)
        Label(master, text=self.description.operationName, anchor=W, justify=LEFT).grid(row=0, column=2, sticky=W)
        Label(master, text=self.description.additionalInfo, anchor=W, justify=LEFT).grid(row=1, column=1, columnspan=3,
                                                                                         sticky=W)
        Label(master, text=self.description.getSoftwareName(), anchor=W, justify=LEFT).grid(row=2, column=1, sticky=W)
        Label(master, text=self.description.getSoftwareVersion(), anchor=W, justify=LEFT).grid(row=2, column=2,
                                                                                               sticky=W)
        Label(master, text='Automated: ' + self.description.automated, anchor=W, justify=LEFT).grid(row=3, column=0,
                                                                                                    sticky=W)
        Label(master, text='User: ' + self.description.username, anchor=W, justify=LEFT).grid(row=3, column=2,
                                                                                                    sticky=E)
        Message(master,
                text='Semantics: ' + ', '.join(self.description.getSemanticGroups()), anchor=W, justify=LEFT).grid(row=4,
                                                                                             column=0,
                                                                                              columnspan=4,
                                                                                              sticky=W)
        row = 5
        if len(self.description.arguments) > 0:
            Label(master, text='Parameters:', anchor=W, justify=LEFT).grid(row=row, column=0, columnspan=4, sticky=W)
            row += 1
            PropertyListFrame(master,self.description).grid(row=row, column=0,columnspan=4, sticky=W+E)
            row += 1
        if self.description.inputMaskName is not None:
            self.inputmaskframe = ButtonFrame(master, self.description.inputMaskName, self.dir, \
                                              label='Mask (' + self.description.inputMaskName + '):', isMask=True,
                                              preserveSnapshot=True)
            self.inputmaskframe.grid(row=row, column=0, columnspan=4, sticky=E + W)
            row += 1
        if self.description.errors and len(self.description.errors) > 0:
            Label(master, text="Errors from mask processing :", anchor=W, justify=LEFT).grid(row=row, column=0,
                                                                                             columnspan=4, sticky=E + W)
            row += 1
            self.errorText = Text(master, height=10, width=40, font=('Times', '14'), relief=RAISED, borderwidth=2)
            self.errorText.grid(row=row, column=0, columnspan=4, sticky=E + W)
            row += 1
            for error in self.description.errors:
                self.errorText.insert(END, error)
            self.errorText.config(state=DISABLED)
        if self.metadiff is not None:
            sections = self.metadiff.getSections()
            Label(master, text=self.metadiff.getMetaType() + ' Changes:', anchor=W, justify=LEFT).grid(row=row,
                                                                                                       column=0,
                                                                                                       columnspan=2 if sections else 4,
                                                                                                       sticky=E + W)
            self.metaBox = MetaDiffTable(master, self.metadiff)
            if sections is not None:
                self.sectionBox = Spinbox(master, values=['Section ' + section for section in sections],
                                          command=self.changeSection)
                self.sectionBox.grid(row=row, column=1, columnspan=2, sticky=SE + NW)
            row += 1
            self.metaBox.grid(row=row, column=0, columnspan=4, sticky=E + W)
            row += 1
        if self.description.maskSet is not None:
            self.maskBox = MaskSetTable(master, self.description.maskSet, dir=self.dir)
            self.maskBox.grid(row=row, column=0, columnspan=4, sticky=SE + NW)
            row += 1

    def changeSection(self):
        self.metaBox.setSection(self.sectionBox.get()[8:])

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def buttonbox(self):
        box = Frame(self)
        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Return>", self.cancel)
        self.bind("<Escape>", self.cancel)
        box.pack()


class ImageNodeCaptureDialog(tkSimpleDialog.Dialog):
    scModel = None
    selectedImage = None

    def __init__(self, parent, scModel):
        self.scModel = scModel
        tkSimpleDialog.Dialog.__init__(self, parent, "Select Node")

    def body(self, master):
        Label(master, text="Node Name:", anchor=W, justify=LEFT).grid(row=0, column=0, sticky=W)
        self.box = AutocompleteEntryInText(master, values=self.scModel.getNodeNames(), takefocus=True)
        self.box.grid(row=1, column=0,sticky=EW)
        self.name_var = StringVar('')
        self.node_name = Label(master, textvariable= self.name_var, anchor=W, justify=LEFT)
        self.node_name.grid(row=2, column=0, sticky=W)
        self.c = Canvas(master, width=500, height=500)
        self.photo = ImageTk.PhotoImage(ImageWrapper(np.zeros((500, 500,3))).toPIL())
        self.imc = self.c.create_image(250, 250, image=self.photo, tag='imgd')
        self.c.grid(row=3, column=0)
        self.box.bind("<Return>", self.newimage)
        self.box.bind("<<ComboboxSelected>>", self.newimage)

    def newimage(self, event):
        im = self.scModel.getImage(self.box.get())
        self.photo = ImageTk.PhotoImage(fixTransparency(imageResize(im, (500, 500))).toPIL())
        self.c.itemconfig(self.imc, image=self.photo)
        self.name_var.set(self.scModel.getGraph().get_node(self.box.get())['file'])

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.selectedImage = self.box.get()


class CompareDialog(tkSimpleDialog.Dialog):
    def __init__(self, parent, im, mask, name, analysis):
        self.im = im
        self.mask = mask
        self.analysis = analysis
        tkSimpleDialog.Dialog.__init__(self, parent, "Compare to " + name)

    def body(self, master):
        self.cim = Canvas(master, width=250, height=250)
        self.photoim = ImageTk.PhotoImage(fixTransparency(imageResizeRelative(self.im, (250, 250), self.im.size)).toPIL())
        self.imc = self.cim.create_image(125, 125, image=self.photoim, tag='imgim')
        self.cim.grid(row=0, column=0)

        self.cmask = Canvas(master, width=250, height=250)
        self.photomask = ImageTk.PhotoImage(fixTransparency(imageResizeRelative(self.mask, (250, 250), self.mask.size)).toPIL())
        self.maskc = self.cmask.create_image(125, 125, image=self.photomask, tag='imgmask')
        self.cmask.grid(row=0, column=1)

        iframe = Frame(master, bd=2, relief=SUNKEN)
        iframe.grid_rowconfigure(1, weight=1)
        Label(iframe, text='  '.join([key + ': ' + str(value) for key, value in self.analysis.items()]), anchor=W,
              justify=LEFT).grid(row=0, column=0, sticky=W)
        iframe.grid(row=1, column=0, columnspan=2, sticky=N + S + E + W)

    def buttonbox(self):
        box = Frame(self)
        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Return>", self.cancel)
        self.bind("<Escape>", self.cancel)
        box.pack()


class VideoCompareDialog(tkSimpleDialog.Dialog):

    def __init__(self, parent, im, mask, name, analysis, dir):
        self.im = im
        self.dir = dir
        self.mask = mask
        self.analysis = analysis
        self.metaBox = None
        self.sectionBox = None
        tkSimpleDialog.Dialog.__init__(self, parent, "Compare to " + name)

    def body(self, master):
        row = 0
        meta_diff = self.analysis['metadatadiff']
        mask_set = self.analysis['videomasks']
        sections = meta_diff.getSections()
        Label(master, text=meta_diff.getMetaType() + ' Changes:', anchor=W, justify=LEFT).grid(row=row, column=0,
                                                                                              columnspan=2 if sections else 4,
                                                                                              sticky=E + W)
        self.metaBox = MetaDiffTable(master, meta_diff)
        if sections is not None:
            self.sectionBox = Spinbox(master, values=['Section ' + section for section in sections],
                                      command=self.changeSection)
            self.sectionBox.grid(row=row, column=1, columnspan=2, sticky=SE + NW)
        row += 1
        self.metaBox.grid(row=row, column=0, columnspan=4, sticky=E + W)
        row += 1
        if mask_set is not None:
            self.maskBox = MaskSetTable(master, mask_set, openColumn=3, dir=self.dir)
            self.maskBox.grid(row=row, column=0, columnspan=4, sticky=SE + NW)
        row += 1

    def changeSection(self):
        self.metaBox.setSection(self.sectionBox.get()[8:])

    def buttonbox(self):
        box = Frame(self)
        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Return>", self.cancel)
        self.bind("<Escape>", self.cancel)
        box.pack()


class FilterCaptureDialog(tkSimpleDialog.Dialog):

    def __init__(self, parent, groupFilterLoader, scModel):
        im, filename = scModel.currentImage()
        self.gfl = groupFilterLoader
        self.pluginOps = self.gfl.getOperations(scModel.getStartType(),None)
        self.im = im
        self.c = None
        self.dir = scModel.get_dir()
        self.parent = parent
        self.scModel = scModel
        self.okButton = None
        self.photo= None
        self.argBox = None
        self.optocall = None
        self.cancelled = True
        self.arginfo = {}
        self.argvalues = {}
        self.softwareLoader = SoftwareLoader()
        self.sourcefiletype = scModel.getStartType()
        tkSimpleDialog.Dialog.__init__(self, parent, os.path.split(filename)[1])

    def body(self, master):
        top = Frame(master)
        bottom = Frame(master)
        self.argBoxMaster = bottom
        self.topBody(top)
        master.pack_propagate(True)
        top.pack_propagate(True)
        bottom.pack_propagate(True)
        top.pack(padx=5, pady=5)
        bottom.pack(padx=5, pady=5)

    def topBody(self,master):
        self.photo = ImageTk.PhotoImage(imageResize(self.im, (250, 250)).toPIL())
        self.c = Canvas(master, width=250, height=250)
        self.c.create_image(128, 128, image=self.photo, tag='imgd')
        self.c.grid(row=0, column=0, columnspan=2)
        self.e1 = AutocompleteEntryInText(master, values=sorted(self.pluginOps.keys(), key=lambda s: s.lower()), takefocus=True)
        self.e1.bind("<Return>", self.newop)
        self.e1.bind("<<ComboboxSelected>>", self.newop)
        row = 1
        labels = ['Plugin Name:', 'Category:', 'Operation:', 'Software Name:', 'Software Version:']
        for label in labels:
            Label(master, text=label, anchor=W, justify=LEFT).grid(row=row, column=0, sticky=W)
            row += 1
        self.catvar = StringVar()
        self.opvar = StringVar()
        self.e1.grid(row=1, column=1)
        row = 2
        variables = [self.catvar, self.opvar]
        for variable in variables:
            Label(master, textvariable=variable, anchor=W, justify=LEFT).grid(row=row, column=1, sticky=W)
            row += 1
        self.softwareselect = MyDropDown(master, sorted(self.softwareLoader.get_names(self.sourcefiletype), key=str.lower), command=self.newsoftware)
        self.versionselect = AutocompleteEntryInText(master, values=[], takefocus=False, width=40)
        self.softwareselect.bind("<Return>", self.newsoftware)
        self.softwareselect.bind("<<ComboboxSelected>>", self.newsoftware)
        self.softwareselect.grid(row=row, column=1, sticky=EW)
        self.softwareselect.set_completion_list(sorted(self.softwareLoader.get_names(self.sourcefiletype), key=str.lower),
                                    initialValue=self.softwareLoader.get_preferred_name())
        self.versionselect.set_completion_list(
            sorted(self.softwareLoader.get_versions(self.softwareLoader.get_preferred_name(),
                                                    software_type=self.sourcefiletype)),
            initialValue=self.softwareLoader.get_preferred_version(self.softwareLoader.get_preferred_name()))

        row += 1
        self.versionselect.grid(row=row, column=1)
        row +=1
        Label(master, text='Parameters:', anchor=W, justify=LEFT).grid(row=row, column=0, columnspan=2)

    def bottomBody(self, master):
        if len(self.pluginOps.keys()) > 0:
            self.newop(None)
        return

    def __getinfo(self,name):
        for k,v in self.arginfo.iteritems():
            if k == name:
                return v
        return None

    def __checkParams(self):
        ok = True
        if self.opvar.get() in [None,'']:
            return False
        for k, v in self.argvalues.iteritems():
            info = self.__getinfo(k)
            if info is None or 'type' not in info:
                continue
            cv, error = checkValue(k, info['type'], v)
            if v is not None and cv is None:
                ok = False
        ok &= checkMandatory(self.scModel.getGroupOperationLoader(),self.opvar.get(), self.sourcefiletype, self.sourcefiletype, self.argvalues)
        return ok

    def __buildTuple(self, argument, arginfo, operation):
        import copy
        argumentTuple = (argument, arginfo)
        argumentTuple = (argument, copy.copy(operation.mandatoryparameters[argument])) if operation is not None and \
                                                                               argument in operation.mandatoryparameters else argumentTuple
        argumentTuple = (argument, copy.copy(operation.optionalparameters[argument])) if operation is not None and \
                                                                              argument in operation.optionalparameters else argumentTuple
        if 'values' in arginfo:
            argumentTuple[1]['values'] = arginfo['values']
        if 'visible' in arginfo:
            argumentTuple[1]['visible'] = arginfo['visible']
        if 'description' not in argumentTuple[1]:
            argumentTuple[1]['description'] = 'Not Available'
        return argumentTuple

    def buildArgBox(self, operationName, arginfo):
        if self.argBox is not None:
            self.argBox.destroy()
        if arginfo is None:
            arginfo = {}
        operation = self.gfl.getOperation(operationName)
        argumentTuples = [self.__buildTuple(arg, arginfo[arg], operation) for arg in arginfo]
        for k, v in operation.mandatoryparameters.iteritems():
            if 'source' in v and v['source'] != self.sourcefiletype:
                continue
            if k in arginfo:
                continue
            argumentTuples.append((k, v))
        for k, v in operation.optionalparameters.iteritems():
            if 'source' in v and v['source'] != self.sourcefiletype:
                continue
            if k in arginfo:
                continue
            argumentTuples.append((k, v))
        for k,v in arginfo.iteritems():
            if 'defaultvalue' in v and v['defaultvalue'] is not None and \
                k not in self.argvalues:
                   self.argvalues[k] =  v['defaultvalue']

        properties = [ProjectProperty(name=argumentTuple[0],
                                      description=argumentTuple[0],
                                      information=argumentTuple[1]['description'],
                                      type=resolve_argument_type(argumentTuple[1]['type'],self.sourcefiletype),
                                      values=operation.getParameterValuesForType(argumentTuple[0], self.sourcefiletype,
                                                                                 argumentTuple[1]['values'] if 'values'
                                                                                 in argumentTuple[1] else []),
                                      value=self.argvalues[argumentTuple[0]] if argumentTuple[
                                                                                    0] in self.argvalues else None) \
                      for argumentTuple in argumentTuples if 'visible' not in argumentTuple[1] or
                           argumentTuple[1]['visible']]
        self.argBox= PropertyFrame(self.argBoxMaster, properties,
                                propertyFunction=EdgePropertyFunction(properties,self.scModel),
                                changeParameterCB=self.changeParameter,
                                dir=self.dir)
        self.argBox.pack()
        self.arginfo = arginfo

    def buttonbox(self):
        box = Frame(self)
        self.okButton = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE,
                               state=ACTIVE if self.__checkParams() else DISABLED)
        self.okButton.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
        self.bind("<Escape>", self.cancel)
        box.pack(side=BOTTOM)

    def changeParameter(self, name, type, value):
        self.argvalues[name] = value
        if name == 'inputmaskname' and value is not None:
            self.inputMaskName = value
        if self.okButton is not None:
            self.okButton.config(state=ACTIVE if self.__checkParams() else DISABLED)

    def newsoftware(self, event):
        sname = self.softwareselect.get()
        self.versionselect.set_completion_list(self.softwareLoader.get_versions(sname,software_type=self.sourcefiletype),
                                    initialValue=self.softwareLoader.get_preferred_version(name=sname))

    def newop(self, event):
        self.argvalues = {}
        if (self.pluginOps.has_key(self.e1.get())):
            self.optocall = self.e1.get()
            op = self.pluginOps[self.optocall]
            opinfo = op['operation']
            self.catvar.set(opinfo['category'])
            self.opvar.set(opinfo['name'])
            if 'software' in opinfo:
                self.softwareselect.set(opinfo['software'])
                self.softwareselect.configure(state='disabled')
            else:
                self.softwareselect.set(self.softwareLoader.get_preferred_name())
                self.softwareselect.configure(state='active')
            self.newsoftware(None)
            self.buildArgBox(opinfo['name'], opinfo['arguments'])
        else:
            self.catvar.set('')
            self.opvar.set('')
            self.softwareselect.set(self.softwareLoader.get_preferred_name())
            self.newsoftware(None)
            self.optocall = None
            self.buildArgBox(None, [])
        if self.okButton is not None:
            self.okButton.config(state=ACTIVE if self.__checkParams() else DISABLED)

    def cancel(self):
        if self.cancelled:
            self.optocall = None
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False
        self.optocall = self.e1.get()
        self.softwaretouse = Software(self.softwareselect.get(), self.versionselect.get())
        if self.softwareLoader.add(self.softwaretouse ):
            self.softwareLoader.save()

class FilterGroupCaptureDialog(tkSimpleDialog.Dialog):
    gfl = None
    im = None
    grouptocall = None
    cancelled = True

    def __init__(self, parent, im, name):
        self.im = im
        self.parent = parent
        self.gfl = GroupFilterLoader()
        tkSimpleDialog.Dialog.__init__(self, parent, name)

    def body(self, master):
        self.photo = ImageTk.PhotoImage(imageResize(self.im, (250, 250)).toPIL())
        self.c = Canvas(master, width=250, height=250)
        self.c.create_image(128, 128, image=self.photo, tag='imgd')
        self.c.grid(row=0, column=0, columnspan=2)
        Label(master, text="Group Name:", anchor=W, justify=LEFT).grid(row=1, column=0, sticky=W)
        self.e1 = AutocompleteEntryInText(master, values=self.gfl.getGroupNames(), takefocus=True)
        self.e1.grid(row=1, column=1)

    def cancel(self):
        if self.cancelled:
            self.grouptocall = None
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False
        self.grouptocall = self.e1.get()

    def getGroup(self):
        return self.grouptocall

class URLCaptureDialog(tkSimpleDialog.Dialog):


    def __init__(self, parent, urls):
        self.urls = [x for x in urls if len(x) > 0]
        tkSimpleDialog.Dialog.__init__(self, parent, "Select URLs")

    def body(self, master):
        yscrollbar = Scrollbar(master, orient=VERTICAL)
        xscrollbar = Scrollbar(master, orient=HORIZONTAL)
        self.listbox = Listbox(master, width=80,
                               yscrollcommand=yscrollbar.set,
                               xscrollcommand=xscrollbar.set)
        self.listbox.bind("<Double-Button-1>", self.remove)
        self.listbox.grid(row=0, column=0, sticky=E + W + N + S,
                               columnspan=3)
        xscrollbar.config(command=self.listbox.xview)
        xscrollbar.grid(row=1, column=0, stick=E + W,columnspan=3)
        yscrollbar.config(command=self.listbox.yview)
        yscrollbar.grid(row=0, column=2, stick=N + S)
        for item in self.urls:
            self.listbox.insert(END, item)
        Label(master, text="Add Entry:", anchor=W, justify=LEFT).grid(row=2, column=0, sticky=W)
        self.url = Text(master, takefocus=True, width=60, height=1, relief=RAISED,
                      borderwidth=2)
        self.url.grid(row=2, column=1,sticky=EW)
        self.url_submit = Button(master, text="Add", command=lambda: self.add(None))
        self.url_submit.grid(row=2, column=2, sticky=W)
        #self.url.bind("<Return>", self.add)

    def buttonbox(self):
        '''add standard button box.

        override if you do not want the standard buttons
        '''

        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Escape>", self.cancel)
        self.bind("<Return>", self.add)
        box.pack()

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        # if len(self.listbox.get(0, END)):

        self.urls = self.listbox.get(0, END)

    def remove(self,event):
        self.listbox.delete(self.listbox.curselection()[0],self.listbox.curselection()[0])

    def add(self,event):
        import urllib2
        contents = self.url.get(1.0, END).strip()
        if not contents.startswith('http'):
            contents = 'http://' + contents
        if len(contents) == 0:
            return
        try:
            f = urllib2.urlopen(contents,timeout=2)
            self.listbox.insert(END, contents)
            f.close()
        except Exception as e:
            pass
        self.url.delete(1.0,END)


class ActionableTableCanvas(TableCanvas):
    def __init__(self, parent=None, model=None, width=None, height=None, allowSave=False,openers=None, **kwargs):
        self.allowSave = True
        self.openers=openers
        TableCanvas.__init__(self, parent=parent, model=model, width=width, height=height, **kwargs)

    def handle_double_click(self, event):
        row = self.get_row_clicked(event)
        self.openFile(row)

    def openFile(self, row):
        #model = self.getModel()
        #TODO: Sorting?
        if self.openers is not None:
            self.openers[row]()

    def saveAll(self):
        self.model.saveAll()

    def popupMenu(self, event, rows=None, cols=None, outside=None):
        """Add left and right click behaviour for canvas, should not have to override
            this function, it will take its values from defined dicts in constructor"""

        defaultactions = {"Set Fill Color": lambda: self.setcellColor(rows, cols, key='bg'),
                          "Set Text Color": lambda: self.setcellColor(rows, cols, key='fg'),
                          "Open": lambda: self.openFile(row),
                          "Copy": lambda: self.copyCell(rows, cols),
                          "Save": self.saveAll,
                          "View Record": lambda: self.getRecordInfo(row),
                          "Select All": self.select_All,
                          "Filter Records": self.showFilteringBar,
                          "Export csv": self.exportTable,
                          "Plot Selected": self.plotSelected,
                          "Plot Options": self.plotSetup,
                          "Export Table": self.exportTable,
                          "Preferences": self.showtablePrefs,
                          "Formulae->Value": lambda: self.convertFormulae(rows, cols)}

        if self.openers is not None:
            main = ["Open", "Set Fill Color", "Set Text Color", "Copy"]
        else:
            main = ["Set Fill Color", "Set Text Color", "Copy"]
        general = ["Select All", "Filter Records", "Preferences"]
        filecommands = ['Export csv','Save'] if self.allowSave else ['Export csv']
        plotcommands = ['Plot Selected', 'Plot Options']
        utilcommands = ["View Record", "Formulae->Value"]

        def createSubMenu(parent, label, commands):
            menu = Menu(parent, tearoff=0)
            popupmenu.add_cascade(label=label, menu=menu)
            for action in commands:
                menu.add_command(label=action, command=defaultactions[action])
            return menu

        def add_commands(fieldtype):
            """Add commands to popup menu for column type and specific cell"""
            functions = self.columnactions[fieldtype]
            for f in functions.keys():
                func = getattr(self, functions[f])
                popupmenu.add_command(label=f, command=lambda: func(row, col))
            return

        popupmenu = Menu(self, tearoff=0)

        def popupFocusOut(event):
            popupmenu.unpost()

        if outside == None:
            # if outside table, just show general items
            row = self.get_row_clicked(event)
            col = self.get_col_clicked(event)
            coltype = self.model.getColumnType(col)

            def add_defaultcommands():
                """now add general actions for all cells"""
                for action in main:
                    popupmenu.add_command(label=action, command=defaultactions[action])
                return

            #            if self.columnactions.has_key(coltype):
            #                add_commands(coltype)
            add_defaultcommands()

        for action in general:
            popupmenu.add_command(label=action, command=defaultactions[action])

        popupmenu.add_separator()
        createSubMenu(popupmenu, 'File', filecommands)
        createSubMenu(popupmenu, 'Plot', plotcommands)
        if outside == None:
            createSubMenu(popupmenu, 'Utils', utilcommands)
        popupmenu.bind("<FocusOut>", popupFocusOut)
        popupmenu.focus_set()
        popupmenu.post(event.x_root, event.y_root)
        return popupmenu


def toNumString(v):
    num = 0
    val = v
    if len(v) > 0 and '0123456789'.find(v[0]) > 0:
        pos = v.find(':')
        if pos < 0:
            pos = len(v)
        try:
            num = float(v[0:pos])
            if pos < len(v):
                val = v[pos+1:]
            else:
                val = ''
        except:
            num = 0
    return num,val

def compareNumString(numstringa,numstringb):
    diff = numstringa[0] - numstringb[0]
    if abs(diff) < 0.00000001:
        return -1 if numstringa[1] < numstringb[1] else (0 if numstringa[1] == numstringb[1] else 1)
    return int(np.sign(diff))

def sortMask(a,b):
    return compareNumString(toNumString(a),toNumString(b))

class ExtendedTableModel(TableModel):

    def __init__(self, datasource):
        TableModel.__init__(self)
        self.changes = {}
        self.datasource = datasource

    def setValueAt(self, value, rowIndex, columnIndex):
        TableModel.setValueAt(self,value,rowIndex,columnIndex)
        if rowIndex not in self.changes:
            self.changes[rowIndex] = {}
        self.changes[rowIndex][columnIndex] = value

    def saveAll(self):
        for row in self.changes:
            for col in self.changes[row]:
                self.datasource.update(row,col, self.changes[row][col])

class MaskSetTable(Frame):
    section = None

    def __init__(self, master, items, dir='.', **kwargs):
        self.items = items
        bh = kwargs.pop('boxheight') if 'boxheight' in kwargs else 125
        bw = kwargs.pop('boxwidth') if 'boxwidth' in kwargs else None
        Frame.__init__(self, master, **kwargs)
        self._drawMe(dir, bw,bh)

    def _drawMe(self, dir, boxWidth=140, boxHeight=125):
        from functools import partial
        openers= []
        model = ExtendedTableModel( self.items)
        for c in self.items.columnNames:
            model.addColumn(c)
        model.importDict(self.items.columnValues)
        model.reclist = sorted(model.reclist)

        def _get_segment(start,file):
            for segment in self.items.maskset:
                if get_start_time_from_segment(segment) == start and \
                    get_file_from_segment(segment) == file:
                    return segment
        def _open_mask(filename, preferences=None,start_frame=None,start_time=0, end_frame=None):
            vid = convertToVideo(filename,preferences=preferences,start_frame=start_frame,start_time=start_time)
            openFile(vid)
        def _log():
            logging.getLogger('maskgen').warn('Empty Segment')
        for recid in model.reclist:
            rec = model.data[recid]
            segment = _get_segment(rec['Start'],rec['File'])
            if segment is not None and get_file_from_segment(segment) is not None:
                opener = partial(_open_mask,
                                 os.path.join(dir,get_file_from_segment(segment)),
                                 preferences=MaskGenLoader(),
                                 start_frame=get_start_frame_from_segment(segment),
                                 start_time=get_start_time_from_segment(segment),
                                 end_frame=get_end_frame_from_segment(segment))
                openers.append(opener)
            else:
                openers.append(_log)

        self.table = ActionableTableCanvas(self, model=model, rowheaderwidth=140, showkeynamesinheader=True,
                                           height=boxHeight, width=boxWidth,
                                           allowSave=True, openers=openers)
        self.table.updateModel(model)
        self.table.createTableFrame()


class MetaDiffTable(Frame):
    section = None

    def __init__(self, master, items, section=None, **kwargs):
        self.items = items
        self.section = section
        Frame.__init__(self, master, **kwargs)
        self._drawMe()

    def setSection(self, section):
        if section == self.section:
            return
        self.section = section
        self.table.getModel().setupModel(self.items.toColumns(section))
        self.table.getModel().reclist = sorted(self.table.getModel().reclist,cmp=sortMask)
        self.table.redrawTable()

    def _drawMe(self):
        model = TableModel()
        for c in self.items.getColumnNames(self.section):
            model.addColumn(c)
        model.importDict(self.items.toColumns(self.section))
        model.reclist = sorted(model.reclist,cmp=sortMask)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)

        self.table = ActionableTableCanvas(self, model=model, rowheaderwidth=140, showkeynamesinheader=True, height=125, allowSave=True)
        self.table.updateModel(model)
        self.table.createTableFrame()


class CompositeCaptureDialog(tkSimpleDialog.Dialog):
    im = None
    cancelled = True
    modification = None
    start_type = None
    end_type = None
    selectMasks = None


    def __init__(self, parent,   scModel ):
        """
        :param parent:
        :param scModel:
        @type scModel : ImageProjectModel
        """
        self.dir = scModel.get_dir()
        self.start_type = scModel.getStartType()
        self.end_type = scModel.getEndType()
        self.parent = parent
        self.scModel = scModel
        name = scModel.start + ' to ' + scModel.end
        self.modification = scModel.getCurrentEdgeModification()
        self.selectMasks = self.scModel.getSelectMasks()
        tkSimpleDialog.Dialog.__init__(self, parent, name)

    def load_overlay(self, event, initialize=False, master=None):
        import logging
        option = self.item.get()
        if option in self.selectMasks.keys():
            finalNode = option
        else:
            finalNode = self.selectMasks.keys()[0]
        value = os.path.split(self.selectMasks[finalNode][0])[1] if self.selectMasks[finalNode] is not None else ''
        self.filename.set(value)
        finalImage = self.scModel.getImageAndName(finalNode)[0]
        imTuple  = self.selectMasks[finalNode]
        color = [0,198,0]
        if imTuple is None:
            red = openImage(get_icon('RedX.png')).to_mask()
            color = [198,0,0]
            im = red.resize(finalImage.size,1)
        else:
            im = imTuple[1]
            im = im.to_mask().invert()
        imResized = imageResizeRelative(im, (250, 250), im.size)
        finalResized = imageResizeRelative(finalImage, (250, 250), finalImage.size)
        try:
            finalResized = finalResized.overlay(imResized,color=color)
        except Exception as ex:
            logging.getLogger('maskgen').error("Improper size mask" + ex.message)
        self.photo = ImageTk.PhotoImage(finalResized.toPIL())
        if initialize:
            self.c = Canvas(master, width=260, height=260)
            self.image_on_canvas = self.c.create_image(0, 0, image=self.photo, anchor=NW)
        else:
            self.c.itemconfig(self.image_on_canvas, image=self.photo)

    def body(self, master):
        self.item = StringVar()
        row = 0
        if len(self.selectMasks.keys()) > 0:
            self.item.set(self.selectMasks.keys()[0] if len(self.selectMasks.keys()) > 0 else '')
            self.filename = StringVar()
            self.load_overlay(None, initialize=True, master=master)
            self.c.grid(row=row, column=0, columnspan=2)
            row += 1
            self.label = Label(master, textvariable=self.filename, justify=LEFT)
            self.label.grid(row=row, column=0, columnspan=2,sticky='EW',padx=10)
            row += 1
            self.optionsBox = ttk.Combobox(master,
                                       values=list(self.selectMasks.keys()),
                                       textvariable=self.item)
            row += 1
            self.optionsBox.grid(row=row, column=0, columnspan=2, sticky='EW')
            self.optionsBox.bind("<<ComboboxSelected>>", self.load_overlay)
            row += 1
            self.bc = Button(master, text="Change Mask", command=self.changemask, relief=FLAT)
            self.bc.grid(row=row, column=0)
            self.bd = Button(master, text="Delete Mask", command=self.deletemask, relief=FLAT)
            self.bd.grid(row=row, column=1)
            row += 1
        self.includeInMaskVar = StringVar()
        op = getOperation(self.modification.operationName)
        self.includeInMaskVar.set(self.modification.recordMaskInComposite)
        if  self.modification.category not in ['Output','AntiForensic','Laundering']  or \
            op is not None and getValue(op.includeInMask, self.start_type, getValue(op.includeInMask,'default',False)):
            self.cbIncludeInComposite = Checkbutton(master, text="Included in Composite", variable=self.includeInMaskVar, \
                                                    onvalue="yes", offvalue="no")
            self.cbIncludeInComposite.grid(row=row, column=0, columnspan=2, sticky=W)
            return self.cbIncludeInComposite
        else:
            return None

    def deletemask(self):
        self.selectMasks[self.optionsBox.get()] = None
        self.load_overlay(None)

    def changemask(self):
        val = tkFileDialog.askopenfilename(initialdir=self.dir, title="Select Input Mask",
                                           filetypes=getMaskFileTypes())
        if (val != None and len(val) > 0):
            self.selectMasks[self.optionsBox.get()]  = (val,openImage(val, isMask=True, preserveSnapshot=os.path.split(os.path.abspath(val))[0] == dir))
            self.load_overlay(None)

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False
        self.modification.setRecordMaskInComposite(self.includeInMaskVar.get())

class SubstituteMaskCaptureDialog(tkSimpleDialog.Dialog):
    cancelled = True

    def __init__(self, parent, scModel ):
        """
        :param parent:
        :param scModel:
        @type scModel : ImageProjectModel
        """
        self.parent = parent
        self.scModel = scModel
        name = scModel.start + ' to ' + scModel.end
        self.use_as_substitute = StringVar()
        self.use_as_substitute.set('yes' if self.scModel.hasSubstituteMasks() else 'no')
        tkSimpleDialog.Dialog.__init__(self, parent, name)

    def body(self, master):
        row=0
        self.ChkButton = Checkbutton(master, text="Use inputmask as substitute", variable=self.use_as_substitute, \
                                                onvalue="yes", offvalue="no")
        self.ChkButton.grid(row=row, column=0, columnspan=2, sticky=W)
        return self.ChkButton

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False



class FileCaptureDialog(tkSimpleDialog.Dialog):

    def __init__(self, parent, name, dir, current_file=None):
        """
        :param parent:
        :param scModel:
        @type scModel : ImageProjectModel
        """
        self.dir = dir
        self.cancelled = True
        self.current_file = os.path.basename(current_file) if current_file is not None else None
        tkSimpleDialog.Dialog.__init__(self, parent, name)

    def body(self, master):
        self.item = StringVar()
        self.item.set(self.current_file if self.current_file is not None else '')
        label1 = Label(master, text='File', justify=LEFT)
        label1.grid(row=0, column=0, columnspan=1, sticky='EW', padx=10)
        label2 = Label(master, textvariable=self.item, justify=LEFT)
        label2.grid(row=0, column=1, columnspan=2, sticky='EW', padx=10)
        self.bc = Button(master, text="Change File", command=self.changefile, relief=RAISED)
        self.bc.grid(row=1, column=0,sticky='EW', padx=10)
        self.bd = Button(master, text="Forget File", command=self.forgetfile, relief=RAISED)
        self.bd.grid(row=1, column=1,sticky='EW', padx=10)

    def forgetfile(self):
        self.item.set('')
        self.current_file = None

    def changefile(self):
        val = tkFileDialog.askopenfilename(initialdir=self.dir, title="Select File",
                                           filetypes=getMaskFileTypes())
        #suffixes = [type_tuple[1][1:] for type_tuple in getMaskFileTypes()]
        if (val != None and len(val) > 0):
            #if os.path.splitext(val)[1] not in suffixes:
            #    return
            self.current_file = os.path.basename(val)
            self.item.set(self.current_file )

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False

class PointsViewDialog(tkSimpleDialog.Dialog):
    """
    View mapping bounding boxes between to images.
    The second image's bounding box can be rotated to align with the first
    """
    cancelled= True
    angle = 0
    level = IntObject()
    colorMap = dict()
    ws = None

    def __init__(self, parent, start_box, end_box, angle, start_im, end_im,model,op=None,argument_name=None):
        """
        :param parent: MakeGenUI
        @type parent: MakeGenUI
        """
        self.parent = parent
        self.startIM = start_im
        self.nextIM = end_im
        self.left_box  = start_box
        self.right_box = end_box
        self.angle = angle
        self.scModel = model
        self.op = op
        self.extender = CompositeExtender(scModel=self.scModel)
        self.argument_name = argument_name
        tkSimpleDialog.Dialog.__init__(self, parent)

    def notify(self,event):
        if self.nb.tab(event.widget.select(), "text") == 'Composite':
            self.composite_view.update(self._newComposite())

    def getStringConfiguration(self):
        return str(self.left_box) + ':' + str(self.right_box) + ':' + str(self.angle)

    def _newComposite(self):
        override_args={
            'op' : self.op,
            'shape change': str((int(self.nextIM.size[1]-self.startIM.size[1]),
                             int(self.nextIM.size[0]-self.startIM.size[0]))).replace('L','')
        }
        if self.argument_name is not None and self.ws is not None:
            self.updateBox()
            override_args['arguments'] = {self.argument_name : self.getStringConfiguration()}

        return self.extender.get_image(override_args,target_size=self.nextIM.size)

    def instructionsFrame(self,master):
        f = Frame(master)
        w1 = Label(f, text="The left image is the image prior to recapture. " + \
                           "The right image is the recaptured image. " + \
                           "The idea is to draw rectangles around the corresponding areas in each. " + \
                           "If a portion of the left image is recaptured, cropping parts of the image, " + \
                           "then draw a rectangle around the portion of the left image that is " + \
                           "captured in the right image. If the recapture image is framed " + \
                           "containing 100% of the left image with additional framing (background), " + \
                           "draw a rectange around the portion of the right image that represents 100% of the " + \
                           "left image.  The rectangle can be adjusted by clicking and dragging the corners."
                   , font=("Helvetica", 14), wraplength=400, justify=LEFT)
        w1.grid(row=0)
        w2 = Label(f, text="Once the rectangles are complete, rotate the right rectangle to indicate the amount of rotation applied to " + \
                           "the image, if any.  In most cases, the amount of rotation is -90,0,90 or 180.",
                   font=("Helvetica", 14), wraplength=400, justify=LEFT)
        w2.grid(row=1)
        w3 = Label(f, text=
        "The composite image tab is for review only.  The refresh button reapplies the changes to the composite, as does " + \
        "switching between the composite tab and the other tabs. The scale is only for aiding the edit process; it is " + \
        "not applied to the final images.",
                   font=("Helvetica", 14), wraplength=400, justify=LEFT)
        w3.grid(row=2)
        return f

    def body(self,master):
        self.nb = ttk.Notebook(master)
        self.left = PictureEditor(master,self.startIM.toPIL(), self.left_box)
        f = self.instructionsFrame(self.nb)

        self.right = PictureEditor(self.nb, self.nextIM.toPIL(),self.right_box,angle=self.angle)
        self.composite_view = ScrollCompositeViewer(self.nb, self.nextIM, self._newComposite())
        self.nb.add(self.right, text='Image')
        self.nb.add(self.composite_view, text='Composite')
        self.nb.add(f, text='Instructions')
        self.nb.select(self.right)
        self.nb.bind('<<NotebookTabChanged>>', self.notify)

        self.left.grid(row=0, column=0, columnspan=2)
        self.nb.grid(row=0, column=2,columnspan=2)

        label1 = Label(master,text='Scale:', justify=RIGHT,anchor=S)
        label1.grid(row=1, column=0,sticky=E)
        self.ls = Scale(master, from_=15, to=100, orient=HORIZONTAL, command=self.rescale)
        self.ls.grid(row=1, column=1,sticky=W)
        self.ls.set(100)
        label2 = Label(master, text='Rotation:', justify=RIGHT,anchor=S)
        label2.grid(row=1, column=2,sticky=E)
        self.ws = Scale(master, from_=-180, to=180,length=360,resolution=1,orient=HORIZONTAL,command=self.rotate)
        self.ws.grid(row=1,column=3,sticky=W)
        self.ws.set(self.angle)

    def rescale(self,event):
        self.right.set_scale(float(self.ls.get())/100.0)
        self.left.set_scale(float(self.ls.get()) / 100.0)
        self.composite_view.set_scale(float(self.ls.get()) / 100.0)

    def rotate(self,event):
        self.right.rotate(self.ws.get())

    def cancel(self):
        tkSimpleDialog.Dialog.cancel(self)

    def refresh_composite(self):
        self.composite_view.update(self._newComposite())

    def updateBox(self):
        if self.ws is not None:
            self.angle = self.ws.get()
            self.left_box = (min(self.left.box[0], self.left.box[2]),
                             min(self.left.box[1], self.left.box[3]),
                             max(self.left.box[0], self.left.box[2]),
                             max(self.left.box[1], self.left.box[3]))
            self.right_box = (min(self.right.box[0], self.right.box[2]),
                              min(self.right.box[1], self.right.box[3]),
                              max(self.right.box[0], self.right.box[2]),
                              max(self.right.box[1], self.right.box[3]))

    def apply(self):
        self.cancelled = False
        self.updateBox()

    def buttonbox(self):
        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=15, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Refresh Composite", width=15, command=self.refresh_composite)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)

        box.pack()


class CommentViewer(tkSimpleDialog.Dialog):
    def __init__(self, master):
        self.master=master
        tkSimpleDialog.Dialog.__init__(self, master)

    def body(self, master):
        try:
            comment = self.master.scModel.getProjectData('qacomment')
            if comment == '':
                raise ValueError('comments missing')
        except ValueError:
            comment = 'There are no comments!'

        self.commentLabel = Label(self, text=comment, wraplength=400, justify=LEFT)
        self.commentLabel.pack(side=TOP)

    def buttonbox(self):
        box = Frame(self)

        w = Button(box, text="Clear Comment", width=15, command=self.clearComment, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="OK", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)

        box.pack()

    def clearComment(self):
        self.master.scModel.setProjectData('qacomment', '',excludeUpdate=True)
        self.commentLabel.config(text='There are no comments!')


class ButtonFrame(Frame):
    def __init__(self, master, fileName, dir, label=None, isMask=False, preserveSnapshot=False, **kwargs):
        Frame.__init__(self, master, **kwargs)
        self.fileName = fileName
        self.dir = dir
        Label(self, text=label if label else fileName, anchor=W, justify=LEFT).grid(row=0, column=0, sticky=W)
        img = openImage(os.path.join(dir, fileName), isMask=isMask, preserveSnapshot=preserveSnapshot)
        self.img = ImageTk.PhotoImage(fixTransparency(imageResizeRelative(img, (125, 125), img.size)).toPIL())
        w = Button(self, text=fileName, width=10, command=self.openMask, default=ACTIVE, image=self.img)
        w.grid(row=1, sticky=E + W)

    def openMask(self):
        openFile(os.path.join(self.dir, self.fileName))



def createCompareDialog(master, im2, mask, nodeId, analysis, dir, linktype):
    if linktype == 'image.image':
        return CompareDialog(master, im2, mask, nodeId, analysis)
    elif linktype == 'video.video':
        return VideoCompareDialog(master, im2, mask, nodeId, analysis, dir)


class RotateDialog(tkSimpleDialog.Dialog):
    parent = None
    cancelled = True
    rotate = 'no'

    def __init__(self, parent, donor_im, rotated_im, orientation):
        self.parent = parent
        self.donor_im = donor_im
        self.rotated_im = rotated_im
        self.orientation = orientation
        tkSimpleDialog.Dialog.__init__(self, parent, "Image Orientation: " + self.orientation)

    def body(self, master):
        row = 0
        Label(master, text="Base").grid(row=row, column=0, sticky=W + S + N)
        Label(master, text="Final w/rotation").grid(row=row, column=1, sticky=E + S + N)
        row = 1
        self.donor_photo = ImageTk.PhotoImage(fixTransparency(imageResize(self.donor_im, (250, 250))).toPIL())
        self.donor_canvas = Canvas(master, width=125, height=125)
        self.donor_canvas.create_image(125, 125, image=self.donor_photo, tag='imgd')
        self.donor_canvas.grid(row=row, column=0, sticky=W + N)
        self.rotated_photo = ImageTk.PhotoImage(fixTransparency(imageResize(self.rotated_im, (250, 250))).toPIL())
        self.rotated_canvas = Canvas(master, width=125, height=125)
        self.rotated_canvas.create_image(125, 125, image=self.rotated_photo, tag='imgr')
        self.rotated_canvas.grid(row=row, column=1, sticky=E + N)
        row = 2
        Label(master, text="Do you wish to counter rotate image").grid(row=row, column=0, sticky=E + W, columnspan=2)
        row = 3
        Label(master, text="to align with the orientation?").grid(row=row, column=0, sticky=E + W, columnspan=2)
        self.buttonboxgrid(master, row + 1)

    def buttonbox(self):
        return

    def buttonboxgrid(self, box, row):
        #        box = Frame(self)
        okButton = Button(box, text="Yes", width=10, command=self.ok, default=ACTIVE)
        okButton.grid(row=row, column=0)
        noButton = Button(box, text="No", width=10, command=self.cancel)
        noButton.grid(row=row, column=1)
        self.bind("<Escape>", self.cancel)
        # box.pack()

    def cancel(self):
        if self.cancelled:
            self.rotate = 'no'
        tkSimpleDialog.Dialog.cancel(self)

    def apply(self):
        self.cancelled = False
        self.rotate = 'yes'


class VerticalScrolledFrame(Frame):
    """
    * Use the 'interior' attribute to place widgets inside the scrollable frame
    * Construct and pack/place/grid normally
    * This frame only allows vertical scrolling
    """

    def __init__(self, parent, horizontal=False, **kw):
        Frame.__init__(self,parent, **kw)
        self.parent = parent
        # create a canvas object and a vertical scrollbar for scrolling it
        self.canvas = canvas = Canvas(self, bd=0, highlightthickness=0)
        vscrollbar = Scrollbar(self, orient=VERTICAL)
        vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
        if horizontal:
            hscrollbar = Scrollbar(self, orient=HORIZONTAL)
            hscrollbar.pack(fill=X, side=BOTTOM, expand=FALSE)
            canvas = Canvas(self, bd=0, highlightthickness=0,
                            yscrollcommand=vscrollbar.set,
                            xscrollcommand=hscrollbar.set)
            hscrollbar.config(command=canvas.xview)
        else:
            canvas = Canvas(self, bd=0, highlightthickness=0,
                            yscrollcommand=vscrollbar.set)
        #canvas.configure(yscrollcommand=vscrollbar.set)
        vscrollbar.config(command=canvas.yview)
        canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)

        # reset the view
        canvas.xview_moveto(0)
        canvas.yview_moveto(0)
        self._canvas = canvas

        #create a frame inside the canvas which will be scrolled with it
        self.interior = Frame(canvas)
        self.interior_id = canvas.create_window(0, 0, window=self.interior,
                                           anchor=NW)

        # track changes to the canvas and frame width and sync them,
        # also updating the scrollbar
        def _configure_interior(event):
            # update the scrollbars to match the size of the inner frame
            size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
            canvas.config(scrollregion="0 0 %s %s" % size)
            if self.interior.winfo_reqwidth() != canvas.winfo_width():
                # update the canvas's width to fit the inner frame
                canvas.config(width=self.interior.winfo_reqwidth())
            screen_h = self.winfo_screenheight()
            if ((self.parent.winfo_rooty() + self.parent.winfo_height() - self.canvas.winfo_height() + self.interior.winfo_reqheight()) < screen_h):
                self.canvas.configure(height=self.interior.winfo_reqheight())

        self.interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(event):
            if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                # update the inner frame's width to fill the canvas
                canvas.itemconfigure(self.interior_id, width=canvas.winfo_width())
            elif self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                self.canvas.config(width=self.interior.winfo_reqwidth())
            if (self.interior.winfo_reqheight() != self.canvas.winfo_height()) or (
                self.interior.winfo_height() != self.canvas.winfo_height()):
                self.canvas.itemconfigure(self.interior_id, height=self.canvas.winfo_height())

        if not horizontal:
            canvas.bind('<Configure>', _configure_canvas)

        return

    def resize_canvas(self, height = 400, width = 400):
        """
        Function for the user to resize the internal Canvas widget if desired
        :param height: new height in pixels
        :param width: new width in pixels
        :return:
        """
        self._canvas.configure(width=width, height=height)

class IntSliderWidget(Frame):

    def __init__(self, master, textvariable=None, label='', range=(0,30), inital_value=0, command=None, **options):
        Frame.__init__(self, master=master)
        self.range = range
        self.length = options['length'] if 'length' in options else None
        self.Value = textvariable if textvariable is not None else StringVar()
        self.entryVar = StringVar()
        self.entryVar.set(textvariable.get() if textvariable is not None else inital_value)
        self.entry = IntEntry(self, textvariable=self.entryVar, range=self.range, callback=self.entryCB)
        self.updateCommand = command
        options['from_'] = range[0]
        options['to'] = range[1]
        options['variable'] = self.Value
        options['command'] = self.updateEntry
        options['showvalue'] = 0
        options['takefocus'] = 1
        if 'orient' not in options:
            options['orient'] = HORIZONTAL
        if 'tickinterval' not in options:
            options['tickinterval'] = range[1]
        self.scale = Scale(master=self, **options)
        self.label = Label(master=self, text=label)

    def grid(self, **options):
        row = getValue(options, 'row', 0)
        column = getValue(options, 'column', 0)
        self.label.grid(row=row, sticky=W)
        self.scale.grid(row=row, column=column + 1, sticky=W + E)

        if self.length is None:
            self.master.update_idletasks()
            master_width = self.master.winfo_width()
            self.scale.config(length=master_width - 60)

        self.entry.grid(row=row, column=column + 2, sticky=E)
        Frame.grid(self, **options)

    def entryCB(self, val):
        try:
            self.set(val)
        except ValueError:
            pass

    def updateEntry(self, new_val):
        entryVal = str(self.entry.get())
        if entryVal != new_val and not (new_val == 0 and entryVal == ''):
            self.entry.set(self.get())

    def get(self):
        return int(self.Value.get())

    def set(self, value):
        if value != self.Value.get():
            self.Value.set(value if self.entry.is_digit(value) else '0')

class IntEntry(Frame):
    whitelist = ['', '-']

    def __init__(self, master, textvariable=None, initial_value=0, label='', range=(0,0), callback=None):
        Frame.__init__(self, master=master)
        self.range = range
        if textvariable is not None:
            self.entryValue = textvariable
        else:
            self.entryValue = StringVar()
            self.entryValue.set(str(initial_value))
        self.callback = callback
        self.label = Label(master=self, text=label)
        self.entry = Entry(master=self,
                           textvariable=self.entryValue,
                           width=6,
                           validatecommand= (self.register(self.validateEntry), '%P', '%V'),
                           validate='all')

    def grid(self, **options):
        row = getValue(options, 'row', 0)
        column = getValue(options, 'column', 0)
        self.label.grid(row=row, sticky=W)
        self.entry.grid(row=row, column=column + 1, sticky=E)
        Frame.grid(self, **options)

    def validateEntry(self, val, event=None):
        is_digit = self.is_digit(val)
        in_range = self.in_range(val)
        valid = True

        if event == 'focusin':
            return valid

        if not is_digit and val not in self.whitelist:
            valid = False
        if not in_range and event == 'focusout':
            self.correct_value()

        if valid is False:
            self.bell()
        self.after_idle(lambda: self.entry.config(validate='all'))

        if valid and self.callback is not None:
            self.callback(val)
        return valid

    def in_range(self, val):
        if not self.is_digit(val):
            return False
        return self.range[0] > val < self.range[1]

    def is_digit(self, val):
        try:
            int(val)
            return True
        except ValueError:
            return False

    def correct_value(self):
        if self.range[0] - self.range[1] != 0:
            new_val = str(self.range[0]) if self.range[0] >= self.get() else self.range[1]
            self.entryValue.set(new_val)

    def get(self):
        return int(self.entryValue.get()) if self.entryValue.get().isdigit() else 0

    def set(self, d):
        d = str(d)
        if self.is_digit(d) or d in self.whitelist:
            if d != '0':
                d = d.lstrip('0')
            self.entryValue.set(d)


class MaskPreviewUI(Toplevel):

    def build_arginfo(self):
        arginfo = []
        op = self.scModel.getGroupOperationLoader().getOperationWithGroups(getValue(self.edge, 'op', None))
        if op is not None:
            for k, v in self.op.mandatoryparameters.iteritems():
                if 'source' in v and v['source'] != self.sourcefiletype:
                    continue
                if 'target' in v and v['target'] != self.targetfiletype:
                    continue
                arginfo.append((k, v))
            for k, v in self.op.optionalparameters.iteritems():
                if 'source' in v and v['source'] != self.sourcefiletype:
                    continue
                if 'target' in v and v['target'] != self.targetfiletype:
                    continue
                arginfo.append((k, v))
        return [(k,v) for k,v in arginfo if k in op.trigger_arguments]

    def layout_image_frames(self):
        from PIL import Image
        raw = self.previewer.frame_to if self.previewer.frame_to is not None else Image.new("RGB", (450, 450), "black")
        raw = imageResizeRelative(raw, (450,450), raw.size)
        diff = getValue(self.previewer.mask_analysis, 'diff', None)
        diff = np.swapaxes(diff, 0,1)
        diff_img = ImageWrapper(diff) if diff is not None else Image.new("RGB", (450, 450), "black")
        diff_img = imageResizeRelative(diff_img, (450, 450), raw.size)
        mask = ImageWrapper(self.previewer.analysis_components.mask, to_mask=True) if \
            self.previewer.analysis_components.mask is not None else Image.new("RGB", (450, 450), "black")
        mask = imageResizeRelative(mask, (450,450), otherImDim=raw.size)
        mask_alpha = mask.toPIL()
        overlay = raw.toPIL() if raw is not None and mask is not None else Image.new("RGB", (450, 450), "black")
        overlay.paste(mask_alpha, mask=mask_alpha.convert('L'))
        return {'overlay':ImageTk.PhotoImage(overlay), 'mask':ImageTk.PhotoImage(mask.toPIL()), 'source':ImageTk.PhotoImage(raw.toPIL())}

    def __init__(self, master, scModel, previewer):

        self.scModel = scModel
        self.previewer = previewer
        self.sourcefiletype = self.scModel.getStartType()
        self.targetfiletype = self.scModel.getEndType()
        self.edge = scModel.G.get_edge(scModel.start, scModel.end)
        self.op = self.scModel.getGroupOperationLoader().getOperationWithGroups(getValue(self.edge, 'op', None))
        self.arginfo = self.build_arginfo()
        self.mod = self.scModel.getCurrentEdgeModification()
        self.master = master
        self.result = None, 0
        video_total_frames = FileMetaDataLocator(self.previewer.analysis_components.file_one).get_frame_count()['frames']
        self.total_frames = int(self.previewer.analysis_components.time_manager.getExpectedEndFrameGiveRate(
            rate=self.previewer.analysis_components.fps, defaultValue=video_total_frames))
        self.hist = None
        self.writing = BooleanVar()
        self.writing.set(FALSE if self.previewer.invalidMask else TRUE)
        Toplevel.__init__(self, master=master)
        self.transient(master=master)
        self.initial_focus = self.body(master=self)
        self.grab_set()
        if not self.initial_focus:
            self.initial_focus = self
        self.protocol(name="WM_DELETE_WINDOW", func=lambda: self.apply('stop'))
        self.initial_focus.focus_set()
        self.body(master=master)
        self.wait_window(self)

    def body(self, master):
        thresh = getValue(self.previewer.mask_analysis, 'threshold', 1)
        self.histogram = HistogramViewer(master=self,
                                         histogram=self.previewer.mask_analysis['hist'],
                                         highlights={'threshold':thresh})
        self.mask_preview = ttk.Notebook(master=self)
        self.pil_images = self.layout_image_frames() # dictionary of labels and images
        self.preview_frames = [Frame(master=self.mask_preview) for _frames in self.pil_images] #list of frame widgets
        images = zip(self.preview_frames, self.pil_images.items())
        self.image_widgets = [Label(master=image[0], image=image[1][1]) for image in images]
        [self.mask_preview.add(child=image[0], text=image[1][0]) for image in images]
        self.mask_preview.select(self.mask_preview.tabs()[1])
        self.frame_select = IntEntry(master=self, label='Generate To: ',
                                     initial_value=self.previewer.analysis_components.one_count,
                                     range=(self.previewer.analysis_components.one_count, self.total_frames))
        self.preview_mode = Checkbutton(master=self.frame_select, text='Write Mask File', variable=self.writing,
                                        state=DISABLED if self.previewer.invalidMask else NORMAL,
                                        command= self.toggle_mask_writing)
        if self.previewer.invalidMask:
            self.invalid_label = Label(master=self.frame_select, text='Mask is now invalid and will not be set.')
        else:
            self.invalid_label = None
        self.frame_limit = Label(master=self.frame_select, text='/{}'.format(self.total_frames))
        op_parameters = self.op.mandatoryparameters.copy()
        op_parameters.update(self.op.optionalparameters)
        self.properties = [ProjectProperty(name=argumentTuple[0],
                                           description=argumentTuple[0],
                                           information=argumentTuple[1]['description'] if
                                      'description' in argumentTuple[1] else '',
                                           type=resolve_argument_type(argumentTuple[1]['type'], self.sourcefiletype),
                                           values=self.op.getParameterValuesForType(argumentTuple[0], self.sourcefiletype),
                                           value=self.previewer.argvalues[argumentTuple[0]] if
                                      argumentTuple[0] in self.previewer.argvalues else None,
                                           defaultvalue=getValue(getValue(op_parameters, argumentTuple[0], {}), 'defaultvalue', None))
                           for argumentTuple in self.arginfo]
        self.property_frame = PropertyFrame(parent=self, properties=self.properties,
                                            propertyFunction=EdgePropertyFunction(self.properties, self.scModel),
                                            changeParameterCB=self.changeParameter,
                                            dir=self.scModel.get_dir())
        self.buttons_frame = Frame(master=self)
        self.generate = Button(master=self.buttons_frame, text='Continue',
                               command=lambda: self.apply('continue'))
        self.cancel = Button(master=self.buttons_frame, text='Cancel', command=lambda: self.apply('stop'))
        self.grid()

    def grid(self, **options):
        Toplevel.grid(self, **options)
        row = getValue(options, 'row', 0)
        column = getValue(options, 'column', 0)
        self.histogram.grid(row=row, column=column, sticky=W)
        [widget.grid() for widget in self.image_widgets]
        self.mask_preview.grid(row=row, column=column + 1, sticky=E)
        self.property_frame.grid(row=row + 2, column=column, columnspan=2, sticky=W+E)
        self.frame_select.grid(row=row + 1, column=column, sticky=W)
        self.frame_limit.grid(row=row+1, column=2, sticky=W)
        if self.invalid_label is not None:
            self.invalid_label.grid(row= row+1, column=3, sticky=W)
        self.preview_mode.grid(row=row+1, column=4, sticky=E)
        self.generate.grid(row=0, column=0, padx=5)
        self.cancel.grid(row=0, column=2, padx=5)
        self.buttons_frame.grid(row=row + 3, column= column, columnspan=3, sticky=S)

    def changeParameter(self, name, type, value):
        self.previewer.argvalues[name] = value
        if name == 'inputmaskname' and value is not None:
            self.inputMaskName = value

        if self.parametersChanged():
            self.frame_select.set(self.previewer.analysis_components.one_count)
            self.frame_select.entry.config(state='readonly')#lock into regenerating the frame.
            self.writing.set(FALSE)
            self.preview_mode.config(state=DISABLED)
        elif not self.previewer.invalidMask:
            self.frame_select.entry.config(state=NORMAL) #unlock
            self.preview_mode.config(state=NORMAL)

    def parametersChanged(self):
        if len(self.previewer.argvalues) != len(self.previewer.compare_args):
            return True
        else:
            for k, v in self.previewer.argvalues.items():
                if k not in self.previewer.compare_args or v != self.previewer.compare_args[k]:
                    return True
        return False

    def toggle_mask_writing(self):
        self.previewer.invalidMask = not self.writing.get()

    def apply(self, result):
        if self.parametersChanged():
            if self.previewer.analysis_components.one_count > 1:
                self.previewer.invalidMask = True #invalidate the mask
            valid_args = self.op.mandatoryparameters.keys()
            valid_args.extend(self.op.optionalparameters.keys())
            self.previewer.argvalues = {k: v for k, v in self.previewer.argvalues.iteritems() if k != 'inputmaskname' and k in valid_args}
            self.previewer.update_args()
            self.scModel.G.update_edge(self.scModel.start, self.scModel.end,
                                       op=self.mod.operationName,
                                       description=self.mod.additionalInfo,
                                       arguments= self.previewer.argvalues,
                                       recordMaskInComposite=self.mod.recordMaskInComposite,
                                       semanticGroups=self.mod.semanticGroups,
                                       editable='no' if (self.mod.software is not None and self.mod.software.internal) or self.mod.operationName == 'Donor' else 'yes',
                                       softwareName=('' if self.mod.software is None else self.mod.software.name),
                                       softwareVersion=('' if self.mod.software is None else self.mod.software.version),
                                       inputmaskname=self.mod.inputMaskName)
            self.scModel._save_group(self.mod.operationName)
            self.scModel.notify((self.scModel.start, self.scModel.end), 'update_edge')
            self.scModel.save()

        self.previewer.frames_to_generate = int(self.frame_select.get()) if int(self.frame_select.get()) != self.total_frames else 'all'
        self.result = {'message': result,
                       'arguments': self.previewer.compare_args}
        self.withdraw()
        self.update_idletasks()
        self.master.focus_set()
        self.destroy()
        return self.result

class HistogramViewer(Frame):
    patches = []

    def __init__(self, master, histogram, highlights= {}, highlight_width=1):
        Frame.__init__(self, master=master)
        self.hist_data = histogram
        self.highlights = highlights
        self.highlight_width = highlight_width
        self.figure = Figure(figsize=(6, 4), dpi=100)
        self.fcanvas = FigureCanvasTkAgg(self.figure, master=self)
        self.subplot = self.figure.add_subplot(111)

    def grid(self, **options):
        Frame.grid(self, **options)
        row = getValue(options, 'row', 0)
        column = getValue(options, 'column', 0)
        self.plot_histogram()
        self.fcanvas.get_tk_widget().grid(row=row, column=column, sticky=N + S + E + W)

    def plot_histogram(self):
        self.subplot.clear()
        self.patches = []
        self.subplot.set_yscale('log')
        self.subplot.plot(self.hist_data, color='blue')
        for label, highlight in self.highlights.items():
            color = 'red'
            span = (highlight-self.highlight_width, highlight+self.highlight_width)
            self.subplot.axvspan(xmin=span[0], xmax=span[1], color=color, alpha=0.5)
            self.patches.append(mpatches.Patch(color=color, label = label + ': ' + str(highlight)))
        self.subplot.legend(handles=self.patches)
        self.fcanvas.draw()

    def set_span(self, span=(0,0)):
        self.highlight = span
        self.plot_histogram()

def notifyCB(obj,name, type, row, cb,a1,a2,a3):
    if cb is not None:
        cb(name, type, obj.values[row].get())

class PropertyFrame(VerticalScrolledFrame):

   parent = None
   cancelled = True
   dir = '.'
   buttons = {}
   propertyFunction = PropertyFunction()
   extra_args = {}
   """
   @type scModel: ImageProjectModel
   """

   def __init__(self, parent, properties, propertyFunction=PropertyFunction(),
                dir='.',
                changeParameterCB=None,
                extra_args ={},
                **kwargs):
     self.parent = parent
     self.properties = [prop for prop in properties if not prop.node and not prop.semanticgroup]
     self.values =   [None for prop in properties]
     self.widgets =[None for prop in properties]
     self.changeParameterCB = changeParameterCB
     self.dir = dir
     self.propertyFunction = propertyFunction
     self.extra_args = extra_args
     VerticalScrolledFrame.__init__(self, parent, **kwargs)
     self.body(self.interior)

   def body(self, master):
       #master = self.interior
       row = 0
       for prop in self.properties:
           self.values[row] = StringVar()
           partialCB = partial(notifyCB,self, prop.name, prop.type, row, self.changeParameterCB)
           self.values[row].trace("w", partialCB)
           p = partial(viewInfo, (prop.description, prop.information))
           Button(master, text=prop.description, takefocus=False, command=p).grid(row=row, sticky=E)
           v = self.propertyFunction.getValue(prop.name)
           if v is not None:
               self.values[row].set(v)
           if prop.type == 'list':
               if prop.readonly:
                  if v is not None and type(v) != list:
                     v = [v]
                  widget = Message(master, text=', '.join(v if v is not None else ''))#,width=80)
                  widget.grid(row=row, column=1, columnspan=2, sticky=E + W)
               else:
                  widget = ttk.Combobox(master, values=prop.values, takefocus=(row == 0),
                                        textvariable=self.values[row], state='readonly')
                  widget.grid(row=row, column=1, columnspan=2, sticky=E + W)
           elif prop.type == 'text':
               widget = Text(master, takefocus=(row == 0), width=60, height=3, relief=RAISED,
                                       borderwidth=2)
               partialf = partial(fillTextVariable, self, row)
               widget.bind("<KeyRelease>", partialf)
               widget.bind("<KeyPress>", partialf)
               if v:
                   widget.insert(1.0, v)
               widget.grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type == 'urls':
               partialf = partial(promptForURLAndFillButtonText, self, prop.name, row)
               v_temp = v if v is not None else '               '
               items  = [re.sub('^http[s]*://','',item) for item in v_temp.split('\n')]
               self.buttons[prop.name] = widget = Button(master, text= ' '.join(items),
                                                         takefocus=False,
                                                        # width=max([len(i) for i in items]),
                                                         height =(1 if v is None else v.count('\n')+1),
                                                         anchor=W, justify=LEFT, padx=2,
                                                         command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type == 'yesno':
               widget = [None,None]
               widget[0]  =Radiobutton(master, text='Yes', takefocus=(row == 0), variable=self.values[row],
                               value='yes')
               widget[0].grid(row=row, column=1, sticky=W)
               #widget[0].deselect()
               widget[1] =  Radiobutton(master, text='No', takefocus=(row == 0), variable=self.values[row], value='no')
               widget[1].grid(row=row, column=2, sticky=E)
               #widget[1].select()
           elif prop.type == 'file:image':
               partialf = partial(promptForFileAndFillButtonText, self, self.dir, prop.name, row, getImageFileTypes())
               self.buttons[prop.name] = widget = Button(master, text=v if v is not None else '              ', takefocus=False,
                                                command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type == 'file:video':
               partialf = partial(promptForFileAndFillButtonText, self, self.dir, prop.name, row, videofiletypes)
               self.buttons[prop.name] = widget = Button(master, text=v if v is not None else '              ',
                                                         takefocus=False,
                                                         command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('file:'):
               typematch = '*.' + prop.type[prop.type.find(':')+1:]
               typename =  prop.type[prop.type.find(':') + 1:].upper()
               partialf = partial(promptForFileAndFillButtonText, self, self.dir, prop.name, row, [(typename, typematch)])
               self.buttons[prop.name] = widget = Button(master, text=v if v is not None else '               ', takefocus=False,
                                                command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('fileset:'):
               initialdir_parts = tuple(prop.type[8:].split('/'))
               initialdir = os.path.join(*tuple(initialdir_parts))
               partialf = partial(promptForFileAndFillButtonText, self, initialdir, prop.name, row, [('Text', '*.txt'), ('All Files', '*')])
               self.buttons[prop.name] = widget = Button(master, text=v if v is not None else '               ', takefocus=False,
                                                command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('folder:'):
               initialdir_parts = tuple(prop.type[7:].split('/'))
               initialdir = os.path.join(*tuple(initialdir_parts))
               partialf = partial(promptForFolderAndFillButtonText, self, initialdir, prop.name, row)
               self.buttons[prop.name] = widget = Button(master, text=v if v is not None else '               ',
                                                         takefocus=False,
                                                         command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('donor'):
               partialf = partial(promptForDonorandFillButtonText, self, prop.name, row)
               self.buttons[prop.name] =  widget = Button(master, text=v if v is not None else '', takefocus=False,
                                                command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('float'):
               widget = Entry(master, takefocus=(row == 0), width=80,textvariable=self.values[row])
               widget.grid(row=row, column=1, columnspan=12, sticky=E + W)
               v = prop.type
           elif prop.type.startswith('int'):
               if prop.type.endswith('slider'):
                   _match = re.search(r"\[(.*?)\]", prop.type).group(1)
                   range = [int(x) for x in _match.split(':')]
                   widget = IntSliderWidget(master, textvariable=self.values[row], range=range, length=300)
               else:
                   widget = Entry(master, takefocus=(row == 0), width=80, textvariable=self.values[row])
               widget.grid(row=row, column=1, columnspan=12, sticky=E + W)
               v = prop.type
           elif prop.type.startswith('boxpair'):
               partialf = partial(promptForBoxPairAndFillButtonText, self, prop.name, row)
               self.buttons[prop.name] = widget = Button(master,
                                                         text=v if v is not None else '',
                                                         takefocus=False,
                                                         command=partialf)
               self.buttons[prop.name].grid(row=row, column=1, columnspan=8, sticky=E + W)
           elif prop.type.startswith('listfromfile:'):
               filename = getFileName(prop.type[13:])
               if filename:
                   with open(filename) as f:
                       values = sorted(f.read().splitlines())
                   widget = ttk.Combobox(master, values=values, textvariable=self.values[row])
               else:
                   logging.getLogger("maskgen").warning(prop.type[13:] + " NOT found.")
                   widget = Entry(master, takefocus=(row == 0), width=80, textvariable=self.values[row])
               widget.grid(row=row, column=1, columnspan=12, sticky=E + W)
           elif prop.type == 'label':
               widget = Label(master, takefocus=(row==0), width=80, text=prop.information)
               widget.grid(row=row, column=0, columnspan=12, sticky=E + W)
           elif prop.type == 'time':
                widget = TimeWidget(master, textvariable=self.values[row])
                widget.grid(row=row, column=1, columnspan=12, sticky=E + W)
           else:
               widget = Entry(master, takefocus=(row == 0), width=80, textvariable=self.values[row])
               widget.grid(row=row, column=1, columnspan=12, sticky=E + W)
           self.widgets[row] = widget
           if prop.readonly:
               if prop.type == 'yesno':
                   widget[0].config(state=DISABLED)
                   widget[1].config(state=DISABLED)
               elif prop.type != 'list':
                   widget.config(state=DISABLED)
           row += 1

   def findWidgetValue(self, widget, prop):
       return widget.get().strip()

   def apply(self):
       i = 0
       for prop in self.properties:
           v = self.findWidgetValue(self.values[i], prop)
           v, error = checkValue(prop.name, prop.type, v)
           if v and len(v) > 0 and error is None:
               self.propertyFunction.setValue(prop.name, v)
           elif error is not None:
               tkMessageBox.showwarning(title=prop.name + ' Error', message=error)
           i += 1



class PropertyListFrame(VerticalScrolledFrame):

   def __init__(self, parent, description,
                **kwargs):
     self.parent = parent
     VerticalScrolledFrame.__init__(self, parent, **kwargs)
     self.description =description
     self.body()

   def body(self):
       master = self.interior
       row = 0
       for argname, argvalue in self.description.arguments.iteritems():
           Label(master, text='      ' + argname + ': ' + str(argvalue), justify=LEFT).grid(row=row, sticky=W)
           row+=1

   def apply(self):
        pass



class ProperyChangeAction:

    """
    Customized actions to take for specific properties by PropertyFunction
    @see PropertyFunction
    """

    def __init__(self):
        pass

    def setvalue(self, oldvalue, newvalue):
        pass

class SystemPropertyFunction(PropertyFunction):

    prefLoader = None
    """
    @type prefLoader: MaskGenLoader
    @type property_change_actions: dict {str:ProperyChangeAction)
    """
    def __init__(self,prefLoader,property_change_actions):
        """

        :param prefLoader:
        @type prefLoader: MaskGenLoader
        """
        self.prefLoader = prefLoader
        self.property_change_actions = property_change_actions


    def getValue(self, name):
        return self.prefLoader.get_key(name,'')

    def setValue(self, name,value):
        if name in self.property_change_actions:
            self.property_change_actions[name].setvalue(self.prefLoader.get_key(name,''), value)
        return self.prefLoader.save(name,value)

class ProjectPropertyFunction(PropertyFunction):

    def __init__(self,scModel):
        """
        :param scModel:
        @type scModel: ImageProjectModel
        """
        self.scModel = scModel

    def getValue(self, name):
        return self.scModel.getProjectData(name)

    def setValue(self, name,value):
        return self.scModel.setProjectData(name,value)

class SystemPropertyDialog(tkSimpleDialog.Dialog):

   cancelled = False
   def __init__(self, parent, properties, prefLoader=None,title="System Properties",
                dir='.',
                property_change_actions=dict()):
        self.properties =properties
        self.prefLoader = prefLoader
        self.dir=dir
        self.property_change_actions = property_change_actions
        tkSimpleDialog.Dialog.__init__(self, parent, title)

   def body(self, master):
        self.vs = PropertyFrame(master,
                            self.properties,
                            propertyFunction=SystemPropertyFunction(self.prefLoader,
                                                                    self.property_change_actions),
                            dir=self.dir)
        self.wm_resizable(width=False,height=True)
        self.master.pack_propagate(True)
        self.master.grid_propagate(True)
        self.vs.pack(side="top", fill="both", expand=True)

   def buttonbox(self):
        '''add standard button box.

        override if you do not want the standard buttons
        '''

        box = Frame(self)

        w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Return>", self.ok)
        self.bind("<Escape>", self.cancel)

        box.pack(expand=False)

   def cancel(self, event=None):
    self.cancelled = True
    tkSimpleDialog.Dialog.cancel(self,event=event)

   def apply(self):
       if not self.cancelled:
            self.vs.apply()
       tkSimpleDialog.Dialog.apply(self)

class PropertyDialog(tkSimpleDialog.Dialog):

   cancelled = False
   def __init__(self, parent, properties, scModel=None,title="Project Properties", dir='.'):
        self.properties =properties
        self.scModel = scModel
        self.dir=dir
        tkSimpleDialog.Dialog.__init__(self, parent, title)

   def body(self, master):
        self.vs = PropertyFrame(master, self.properties,
                           propertyFunction=ProjectPropertyFunction(self.scModel),
                            dir=self.dir)
        self.vs.grid(row=0)

   def cancel(self, event=None):
    self.cancelled = True
    tkSimpleDialog.Dialog.cancel(self,event=event)

   def apply(self):
       if not self.cancelled:
            self.vs.apply()
       tkSimpleDialog.Dialog.apply(self)

class EdgePropertyFunction(PropertyFunction):

    lookup_values = {}

    scModel = None
    def __init__(self,  properties, scModel):
        """
        :param properties:
        :param scModel:
        @type properties: list
        @type scModel: ImageProjectModel
        """
        self.scModel = scModel
        for prop in properties:
            self.lookup_values[prop.name] = prop.value if prop.value is not None else prop.defaultvalue

    def getValue(self, name):
        return self.lookup_values[name]

    def setValue(self, name,value):
        self.lookup_values[name] = value

class NodePropertyFunction(PropertyFunction):

    def __init__(self,  properties):
        """
        """
        self.lookup_values = properties

    def getValue(self, name):
        return  self.lookup_values[name] if name in self.lookup_values else None

    def setValue(self, name,value):
        self.lookup_values[name] = value


class ValidationFrame(VerticalScrolledFrame):

    def __init__(self, master, parent,items,**kwargs):
        """

        :param master:
        :param parent:
        :param items:
        :param kwargs:
        @type items: list of ValidationMessage
        """
        VerticalScrolledFrame.__init__(self, master,horizontal=True,**kwargs)
        self.parent = parent
        self.body(self.interior, items)
        self.fixes = []
        self.items = items

    def errorMessagesIncomplete(self):
        """
              Unrepaired error messages still in the list?
              :return: bool
              @rtype: bool
              """
        for row in range(len(self.items)):
            item = self.items[row]
            if item.Severity in [Severity.CRITICAL,Severity.ERROR] and row not in self.fixes:
                return True
        return False

    def autofixesComplete(self):
        """
        Are some repairable errors not repaired?
        :return: bool
        @rtype: bool
        """
        for row in range(len(self.items)):
            item = self.items[row]
            if item.Fix is not None and row not in self.fixes:
                return False
        return True


    def fix(self, row, item):
        """
        :param row:
        :param item:
        :return:
        @type item: ValidationMessage
        """
        try:
            item.applyFix(self.parent.scModel.getGraph())
            self.widgets[row - 1].grid_forget()
            self.buttons[row - 1].grid_forget()#config(state=DISABLED)
            self.fixes.append(row - 1)
        except Exception as ex:
            tkMessageBox.showwarning('Error' ,str(ex))

    def body(self,master, items):
        """
        :param parent:
        :param items:
        :return:
        @type items: list of ValidationMessage
        """
        row = 1
        self.buttons = []
        self.widgets = []
        for item in items:
            if item.Start != item.End:
                item_text='{}: {}->{} {}'.format(item.Severity.name,
                                                                self.parent.scModel.getFileName(item.Start),
                                                                self.parent.scModel.getFileName(item.End),
                                                                item.Message)
            elif len(item[1]) > 0:
                item_text=  '{}: {} {}'.format(item.Severity.name,
                                                            self.parent.scModel.getFileName(item.Start),
                                                            item.Message)
            else:
                item_text= '{}: {}'.format(item.Severity.name,
                                                         item.Message)

            cb = partial(self.parent.selectLink,item.Start,item.End)
            cbfix = partial(self.fix,row,item)
            widget = Button(master, text=item_text, command=cb)
            widget.config(relief=SUNKEN)
            button = Button(master, text='Fix', takefocus=False, command=cbfix)
            button.config(state=DISABLED if item.Fix is None else ACTIVE)
            self.buttons.append(button)
            self.widgets.append(widget)
            button.grid(row=row, column=2,sticky=E)
            widget.grid(row=row, column=1,sticky=W)
            row+=1


class ValidationListDialog(Toplevel):
    items = None

    def __init__(self, parent, items, name):
        """
        :param items:
        :return:
        @type items: list of ValidationMessage
        """
        self.items = items
        self.parent = parent
        Toplevel.__init__(self, parent)
        self.resizable(width=True, height=True)
        self.title(name)
        self.parent = parent
        body = Frame(self)
        body.pack(padx=5, pady=5, fill=BOTH, expand=True)
        self.itemsframe = ValidationFrame(body, parent,items)
        self.itemsframe.grid(row=0, column=0, sticky=N + E + S + W)
        self.grid_propagate(True)
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        body.grid_rowconfigure(0, weight=1)
        body.grid_columnconfigure(0, weight=1)
        w = self.buttons(body)
        w.grid(row=2, column=0)
        self.bind("<Return>", self.cancel)
        self.bind("<Escape>", self.cancel)
        self.protocol("WM_DELETE_WINDOW", self.cancel)
        self.geometry("+%d+%d" % (parent.winfo_rootx() + 50,
                                  parent.winfo_rooty() + 50))

    def buttons(self, frame):
        return Button(frame, text="OK", width=10, command=self.cancel, default=ACTIVE)

    def cancel(self):
        # self.parent.doneWithWindow(self)
        self.parent.focus_set()
        self.destroy()


class DecisionValidationListDialog(ValidationListDialog):
    isok = False

    def __init__(self, parent, items, name):
        ValidationListDialog.__init__(self, parent, items, name)

    def autofixesComplete(self):
        return self.itemsframe.autofixesComplete()

    def errorMessagesIncomplete(self):
        return self.itemsframe.errorMessagesIncomplete()

    def setok(self):
        self.isok = True
        self.cancel()

    def wait(self, root):
        root.wait_window(self)

    def buttons(self, frame):
        box = Frame(frame)
        w1 = Button(box, text="Cancel", width=10, command=self.cancel, default=ACTIVE)
        w2 = Button(box, text="Continue", width=10, command=self.setok, default=ACTIVE)
        w1.pack(side=LEFT, padx=5, pady=5)
        w2.pack(side=RIGHT, padx=5, pady=5)
        return box
