#!/usr/bin/env python
################################################################################
#
#       This file is part of the GQL (Graphical Query Language) Toolkit 
#
#       file:   GQLCluster.py
#       author: Alexander Schliep (alexander@schliep.org)
#
#       Copyright (C) 2003 Alexander Schliep
#                                   
#       Contact: alexander@schliep.org           
#
#       Information: http://ghmm.org/gql
#
#	GQL is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	GQL is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with GQL; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
#
#       This file is version $Revision: 321 $ 
#                       from $Date: 2004-01-21 17:15:54 -0300 (Wed, 21 Jan 2004) $
#             last change by $Author: schliep $.
#
################################################################################
from Tkinter import *
from ScrolledText import *
import tkSimpleDialog 
import tkMessageBox
from tkColorChooser import askcolor
import copy
import sys
import os
import types


def typed_assign(var, val):
    result = type(var)(val)
    result.__dict__ = copy.copy(var.__dict__)
    return result



#-------------------------------------------------------------------------------
class TkStringEntry:
    """Tk entry field for editing strings"""

    def __init__(self, master, width):
        self.entryWidget = Entry(master, width=width, exportselection=FALSE)
        
    def tkWidget(self):
        return self.entryWidget

    def get(self):
        return self.entryWidget.get()
    
    def set(self, value):
        self.entryWidget.delete(0,END)
        self.entryWidget.insert(0,"%s" % value)

    def select(self):    
        self.entryWidget.selection_range(0,"end")
        self.entryWidget.focus_set()

        
class TkIntEntry(TkStringEntry):
    """Tk entry field for editing one integer"""

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

    
class TkFloatEntry(TkStringEntry):
    """Tk entry field for editing one float"""

    def get(self):
        return float(self.entryWidget.get())
    

class TkDefaultMixin:
    """Mixin for TkStringEntry, TkIntEntry, TkFloatEntry, ... to deal with
       values which have an externally defined default value. Combination
       of 'use default' checkbox and corresponding entry field """

    def __init__(self, master, useDefault, defaultValue):
        self.frame = Frame(master, relief=FLAT)
        self.useDefault = IntVar()
        self.useDefault.set(useDefault)
        self.defaultValue = defaultValue
        useDefaultButton = Checkbutton(self.frame, text="Use default",
                                       variable=self.useDefault,
                                       command=self.toggleDefault)
        useDefaultButton.grid(row=0, column=0, padx=4, pady=3, sticky=W)

    def finish(self):
        self.entryWidget.grid(row=0, column=1, padx=4, pady=3, sticky=W)
        self.switchDefault(self.useDefault.get())   
        
    def UseDefault(self):
        return self.useDefault.get()

    def switchDefault(self, value):
        if value == 0:
            self.entryWidget['state'] = NORMAL
            self.entryWidget.delete(0,END)
            self.set(self.defaultValue)
        else:
            self.entryWidget.delete(0,END)
            self.entryWidget['state'] = DISABLED
            
    def toggleDefault(self):
        self.switchDefault(self.useDefault.get())            


class TkDefaultStringEntry(TkStringEntry, TkDefaultMixin):

    def __init__(self, master, width, useDefault, defaultValue):
        TkDefaultMixin.__init__(self, master, useDefault, defaultValue)
        TkStringEntry.__init__(self, self.frame, width)
        self.finish()        

    def tkWidget(self): # To avoid ambiguity
        return self.frame


class TkDefaultIntEntry(TkIntEntry, TkDefaultMixin):

    def __init__(self, master, width, useDefault, defaultValue):
        TkDefaultMixin.__init__(self, master, useDefault, defaultValue)
        TkIntEntry.__init__(self, self.frame, width)
        self.finish()        

    def tkWidget(self): # To avoid ambiguity
        return self.frame

    def get(self):
        if self.UseDefault():
            return self.defaultValue
        else:
            return TkIntEntry.get(self)

class TkDefaultFloatEntry(TkFloatEntry, TkDefaultMixin):

    def __init__(self, master, width, useDefault, defaultValue):
        TkDefaultMixin.__init__(self, master, useDefault, defaultValue)
        TkFloatEntry.__init__(self, self.frame, width)
        self.finish()        

    def tkWidget(self): # To avoid ambiguity
        return self.frame

    def get(self):
        if self.UseDefault():
            return self.defaultValue
        else:
            return TkFloatEntry.get(self)



class TkPopupSelector:
    def __init__(self, master, value2pop, pop2value, width):

        self.value2pop = value2pop
        self.pop2value = pop2value
        self.popupvalue = StringVar()
        self.popupvalue.set(self.pop2value.keys()[0]) # XXX first value as default 

        # XXX Uuughhh
        keys = self.value2pop.keys()
        keys.sort()
        pops = map(lambda x: value2pop[x], keys)
        #log.debug("pops = %s" % pops)
        args = (master, self.popupvalue) + tuple(pops)
            
        self.tkwidget = apply(OptionMenu, args)
        self.tkwidget.config(height=1, width=width)

    def tkWidget(self):
        return self.tkwidget

    def get(self):
        return self.pop2value[self.popupvalue.get()]

    def set(self, value):
        try:
            self.popupvalue.set(self.value2pop[value])
        except:
            self.popupvalue.set(self.pop2value.keys()[0]) # XXX first value as default       

    def select(self):    
        # Cant choose invalid value with popup
        pass
    
class TkStringPopupSelector:
    def __init__(self, master, strings):

        self.strings = strings
        self.popupvalue = StringVar()
	if len(self.strings) > 0:
            self.popupvalue.set(self.strings[0]) # XXX first value as default 

	width = max(map(len, self.strings))
        args = (master, self.popupvalue) + tuple(self.strings)
        self.tkwidget = apply(OptionMenu, args)
        self.tkwidget.config(height=1, width=width)

    def tkWidget(self):
        return self.tkwidget

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

    def set(self, value):
        try:
            self.popupvalue.set(value)
        except:
            self.popupvalue.set(self.strings[0]) # XXX first value as default       

    def select(self):    
        # Cant choose invalid value with popup
        pass
    

class TkColorSelector:
    def __init__(self, master, color='black'):
        #self.tkwidget = Button(master, width=8, command=self.editColor)
        self.tkwidget = Frame(master, height=18, width=60, relief=RIDGE, borderwidth=1)
        self.tkwidget.bind("<ButtonRelease-1>", self.editColor)
	self.set(color)

    def editColor(self, event):
        color = askcolor(self.color)[1]
	if color is not None:
            self.set(color)
	
    def tkWidget(self):
        return self.tkwidget

    def get(self):
        return self.color

    def set(self, value):
        self.color = value
	self.tkwidget.config(bg=self.color)

    def select(self):    
        # Cant choose invalid value with popup
        pass



class EditObjectAttributesDialog(tkSimpleDialog.Dialog):
    """ Creates an editable (pseudo-)inspector for a selected set of
        attributes of a given object

         - master : tk master widget
         - object : the object, whose attributes we want to edit
         - attr_names : a list of attr_names

        By making use of Python 2.2's capability of subclassing built-in
        types such as ints, information about editing etc. is conveyed.
        An attr must have:
         - validate(value) method [return 1, if value is a valid new value for attr]

        The class of an attr can have the following mix-ins:
         - Popubable 
         - WithDefault 
    """

    def __init__(self, master, object, attr_names):
        self.object = object
        self.attr_names = attr_names
        self.edit = {}
        self.result = None
	tkSimpleDialog.Dialog.__init__(self, master, "Edit: %s" % self.object.desc)

        
    def editWidget(self, master, object, attr_name):
        """ Create a widget capable of editing attr and insert attr's current value"""

        attr = object.__dict__[attr_name]
        attr_type = type(attr)
        widget = None
        default = isinstance(attr, WithDefault) # has a WithDefault mixin

        if isinstance(attr, Popupable):            
            widget = TkPopupSelector(master, attr.val2pop, attr.pop2val, attr.width)

        elif isinstance(attr, str):

            if default:
                widget = TkDefaultStringEntry(master, max(32, len(attr)), attr.useDefault, attr)
            else:
                widget = TkStringEntry(master, max(32, len(attr)))

        elif isinstance(attr, int):

            if default:
                widget = TkDefaultIntEntry(master, 6, attr.useDefault, attr)
            else:
                widget = TkIntEntry(master, 6)

        elif isinstance(attr, float):

            if default:
                widget = TkDefaultFloatEntry(master, 8, attr.useDefault, attr)
            else:
                widget = TkFloatEntry(master, 8)

        widget.set(attr)
        return widget

        
    def body(self, master):
	self.resizable(0,0)	

        # Header Zeile
	label = Label(master, text="Name", anchor=E)
	label.grid(row=0, column=0, padx=4, pady=3, sticky=E)
	label = Label(master, text="Value", anchor=W)
	label.grid(row=0, column=1, padx=4, pady=3, sticky=W)

        cur_row = 1

        for attr in self.attr_names:
            label = Label(master, text="%s" % attr, anchor=E)
	    label.grid(row=cur_row, column=0, padx=4, pady=3, sticky=E)
	    
	    self.edit[attr] = self.editWidget(master, self.object, attr)
            if self.edit[attr] != None:
                self.edit[attr].tkWidget().grid(row=cur_row, column=1, padx=2, pady=1, sticky=W)

            cur_row = cur_row + 1

    def validate(self):
        for attr_name in self.edit.keys():
            try:
                
                # In python 2.2 we can subclass attributes and add a validate method
                # to attributes

                value = self.edit[attr_name].get()

                if self.object.__dict__[attr_name].validate(value) == 0:
                    raise ValueError

	    except ValueError:
		msg = "Please enter a valid value for %s" % attr_name
		tkMessageBox.showwarning("Invalid Value", msg, parent=self)
                self.edit[attr_name].select()
		return 0
        
        # Everything is valid => set values
        for attr_name in self.edit.keys():            
            self.object.__dict__[attr_name] = typed_assign(self.object.__dict__[attr_name], self.edit[attr_name].get())

            if isinstance(self.object.__dict__[attr_name], WithDefault):
                self.object.__dict__[attr_name].useDefault = self.edit[attr_name].useDefault.get()

           
	return 1


    def ok(self, event=None):
        tkSimpleDialog.Dialog.ok(self, event)
        self.result = 1

    def cancel(self, event=None):
        tkSimpleDialog.Dialog.cancel(self, event)
        if self.result == None: # ok in tkSimpleDialog.Dialog calls cancel
            self.result = 0



#-------------------------------------------------------------------------------
class WithDefault:
    """Mix-in for variables which have a default value"""

    def setDefault(self, useDefault, defaultValue):
        self.useDefault = useDefault
        self.defaultValue = defaultValue

    def validate(self, value):
##        if self.useDefault:
##            return 1
##        else:
##            return 1 # XXX How can I call a method of the class I am mixed too
        return 1


class Popupable:
    """Mix-in for variables which can be edited via a pop-up menu
       - val2pop : dict mapping value to string for pop up menu
       - pop2val: dict mapping pop up menu string to value
       - width: maximal string length in pop up
    """
    def setPopup(self, val2pop, pop2val = None, width = None):
        self.val2pop = val2pop
        self.pop2val = None
        self.width = None

        if pop2val == None:
            self.pop2val = {} # Private copy
            self.width = 0
        
            for val in val2pop.keys():
                pop = val2pop[val]
                self.width = max(len(pop), self.width)
                self.pop2val[pop] = val
        else:
            self.pop2val = pop2val
            self.width = width
           
    def validate(self, value):
        return 1

##class PopupableStr(str):
##    """Class for variables which can be edited via a pop-up menu
##       - values: array of values 
##       - width: maximal string length in pop up
##    """
##    def setPopup(self, values, width = None):

##        self.values = values
##        self.width = width

##        if width == None:
##            self.width = 0
        
##            for s in values:
##                self.width = max(len(s), self.width)
           
##    def validate(self, value):
##        return 1


class AlwaysValidate:
    """Mix-in for variables which always are valid"""
    def validate(self, value):
        return 1

#-------------------------------------------------------------------------------
class ValidatingInt(int, AlwaysValidate):
    """Editable replacement for ints"""
    pass

class  ValidatingFloat(float, AlwaysValidate):
    """Editable replacement for floats"""
    pass
    
class  ValidatingString(str, AlwaysValidate):
    """Editable replacement for strings"""
    pass

class PopupableInt(int, Popupable):
    """A replacement for ints editable via a pop-up"""
    pass

class Probability(float):
    """An editable float taking values from [0,1]"""
    def validate(self, value):
        if 0.0 <= value and value <= 1.0:
            return 1
        else:
            return 0

class DefaultedInt(int, WithDefault):
    """An editable int with a default value"""    
    pass

class DefaultedFloat(float, WithDefault):
    """An editable float with a default value"""    
    pass

class DefaultedString(str, WithDefault):
    """An editable strinf with a default value"""    
    pass


#======================================================================
#
# Demo:
#
class TkTestFrame(Frame):

    def __init__(self, parent=None):
	Frame.__init__(self,parent)
        Pack.config(self)
	self.createWidgets()

        self.desc = ValidatingString("The TkTestFrame")
        self.x = DefaultedInt(1)
        self.x.setDefault(1, 122)
        self.y = ValidatingFloat(2.33)
        self.choose = PopupableInt(3)
        self.pop2val = {"aaa":1, "xxx":2, "sss":3}
        self.val2pop = {1:"aaa", 2:"xxx", 3:"sss"}
        self.choose.setPopup(self.val2pop, self.pop2val, 5)

    def createWidgets(self):
        self.QUIT = Button(self, text='QUIT', foreground='red', 
                           command=self.quit)
        self.QUIT.pack(side=LEFT)
        self.About = Button(self, text='Preferences', foreground='red', 
                           command=self.About)
        self.About.pack(side=LEFT)


    def About(self):
	aboutBox = EditObjectAttributesDialog(self.master, self, ['desc', 'x', 'y', 'choose'])
        del self.pop2val["aaa"]
        del self.val2pop[1]
	aboutBox = EditObjectAttributesDialog(self.master, self, ['desc', 'x', 'y', 'choose'])

if __name__ == '__main__':
    app = TkTestFrame()
    app.mainloop()


