
import tkinter as Tk

class ModalWindow(Tk.Toplevel):
    # 
    # Makes a toplevel window that is model (won't let you access the parent window when this window is open)
    # self.pack_and_display() = packs any buttons in the window and displays the window
    #
    # parent = window to display on top of
    # buttons = True to include a default set of "Accept" and "Cancel" buttons in the window
    # user_accept = the function that the "Accept" button should call before destroying itself
    # user_cancel = the function that the "Cancel" button should call before destroying itself
    #
    def __init__(self, parent, title='modal window', buttons=False, user_accept=False, user_cancel=False, *posargs, **kwargs):
        Tk.Toplevel.__init__(self, *posargs, **kwargs)
        self.parent_window = parent
        self.buttons = buttons
        self.user_accept = user_accept
        self.user_cancel = user_cancel
        self.parent_window = parent
        self.title(title)
        self.wm_protocol('WM_DELETE_WINDOW', self.cancel)
        self.withdraw()

    def cancel(self):
        # The user should define a function and assign it to the modal window as follows:
        # bob = modal_window(parent, user_cancel=function)
        if self.user_cancel:
            self.user_cancel()
        self.destroy()

    def accept(self,event):
        # The user should define a function and assign it to the modal window as follows:
        # bob = modal_window(parent, user_accept=function)
        if self.user_accept:
            self.user_accept(event)
        self.grab_release()
        self.destroy()

    def display(self, grab=True):
        #print "ModalWindow::display"
        # Don't include the height and width in the geometry string or else the window doesn't automatically resize
        self.update_idletasks()
        self.deiconify()
        # Transient makes this window not show up in the taskbar, and iconify, etc. with parent
        self.transient(self.parent_window)
        # focus_set makes this window active, which Windows doesn't always do without it
        self.focus_set()

        # The grab_set/wait_window combo means that this window stays on top and the parent
        # window doesn't respond to any mouseclicks
        if grab:
            # We may want to set grab=False if this window has widgets that generate another toplevel window 
            # (such as a DropDownCombobox)
            self.grab_set()
            #self.parent_window.wait_visibility(self.parent_window)
        self.parent_window.wait_window(self)
    
    def pack(self,x,y):
        if self.buttons:
            frame = Tk.Frame(self)
            accept_button = Tk.Button(frame, text='Accept', command=self.accept)
            cancel_button = Tk.Button(frame, text='Cancel', command=self.cancel)
            frame.pack(fill=Tk.X)
            accept_button.pack(side=Tk.LEFT)
            cancel_button.pack(side=Tk.RIGHT)
            self.buttons = False
        #print "Pack=(%d,%d)" % (x,y)

    def pack_and_display(self,event=None,grab=True):
        #print "ModalWindow::pack_and_display"
        wgt = event.widget
        (x,y) = wgt.canvasx(event.x), wgt.canvasy(event.y)
        self.pack(x,y)
        self.display(grab=grab)

def get_val():
    myvar.set(entry.get())


if __name__=="__main__":
    root = Tk.Tk()
    myvar = Tk.StringVar()
    myvar.set('Hello')
    label = Tk.Label(textvariable=myvar)
    mywin = ModalWindow(root, buttons=True, user_accept=get_val)
    button = Tk.Button(root, text='Show window', command=mywin.pack_and_display)
    entry = Tk.Entry(mywin)
    entry.pack()
    label.pack()
    button.pack()
    root.mainloop()

