import os
import subprocess
import sys
import glob
import re
import matplotlib
matplotlib.use('TkAgg')
from matplotlib import pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter
import tkinter.font
from tkinter.font import Font, nametofont
from tkinter import colorchooser
#will be implemented in tk8.6.13")
#from tkinter import fontchooser

from tklib.tkutils import pint, pfloat, pconv, is_file, get_file_masks, getenv, get_os
from tklib.tkutils import get_file_list, split_file_path, find_executable_path
from tklib.tkparams import tkParams

def set_variable_from_list(app, key, var):
    app.tkvars.set_attr(key, var)
    return var

def execute_command(app, command, working_dir = None, is_print = False, files = []):
#    print("command=", command)
#    print("working_dir=", working_dir)
    a = command.split('[')
    if len(a) >= 2:
        command, arg = a[0], a[1]
        a = arg.split(']')
        if len(a) >= 2:
            arg = a[0]
        arg = f'{arg}'
    else:
        arg = ''

    command = command.strip()
    arg = arg.strip()
    
    if working_dir is not None:
        arg = arg.replace('%(dir)', working_dir)
    if command == 'auto':
        cmd = ''
    elif ' ' in command:
        cmd = f'"{command}"'
    else:
        cmd = f'{command}'

    cmd = f'{cmd} {arg}'
    if arg == '':
        for s in files:
            if ' ' in s:
                cmd += f' "{s}"'
            else:
                cmd += f' {s}'

    if is_print:
        print(f"cmd: [{cmd}]")

    return os.system(cmd)

def folder_button_click(app, entry_variable, ini_dir = '.', title = None, mustexist = True, 
            entry_type = 'entry', entry_filemask = '*;*.*'):
    seldir = tkinter.filedialog.askdirectory(initialdir = ini_dir, title = title, mustexist = mustexist)
    if seldir is not None and seldir != '':
        entry_variable.set(seldir)
        entry_variable.prev_val = selpath
        if entry_type == 'combobox':
            dirs, files = get_file_list(selpath, entry_filemask)
            for f in files:
                dirs.append(f)
            entry_variable['values'] = dirs

def path_button_click(app, entry_variable, ini_dir = '.', title = None, file_type = [("All", "*.*")], 
            entry_type = 'entry', entry_filemask = '*;*.*', check_exist = True):
    if check_exist:
        selpath = tkinter.filedialog.askopenfilename(filetypes = file_type, initialdir = ini_dir, title = title)
    else:
        selpath = tkinter.filedialog.asksaveasfilename(filetypes = file_type, initialdir = ini_dir, title = title)
    if selpath is not None and selpath != '':
        entry_variable.set(selpath)
        entry_variable.prev_val = selpath
        if entry_type == 'combobox':
            dirs, files = get_file_list(selpath, entry_filemask)
            for f in files:
                dirs.append(f)
            entry_variable['values'] = dirs

def edit_button_click(app, entry_variable, editor_path = None):
    if editor_path is editor_path:
        editor_path = app.get_editor()
    path = entry_variable.get()
    if ' ' in editor_path:
        editor_path = f'"{editor_path}"'
    if ' ' in path:
        path = f'"{path}"'
    cmd = editor_path + ' ' + path
    print(f"* Execute [{cmd}]")
    os.system(cmd)
#   subprocess.call(cmd)
        
def shell_button_click(app, cmd = None):
    comspec = getenv('ComSpec', 'cmd.exe')
    if cmd is None:
       cmd = f'start {comspec}'
    print(f"* Execute [{cmd}]")
    os.system(cmd)

def color_dialog(app = None, parent = None, title = None, color = None):
    return colorchooser.askcolor(parent = parent, title = title, color = color)
    return colorchooser.askcolor(parent = app.root_window, title = title, color = color)

def font_dialog(app = None, parent = None, title = None, color = None):
#    return fontchooser.askcolor(parent = parent, title = title, color = color)
    sel = tkParams()
    sel.font = None
    def font_changed(sel, sel_font):
        sel.font = sel_font

    app.root_window.tk.call('tk', 'fontchooser', 'configure', '-font', 'helvetica 24', 
            '-command', app.root_window.register(lambda sel_font: font_changed(sel, sel_font)))
    app.root_window.tk.call('tk', 'fontchooser', 'show')
    return sel.font
    
def dialog_askfloat(app, title = 'Input float value', message = 'Input float value',
                        initialvalue = None, minvalue = None, maxvalue = None):
        return tkinter.simpledialog.askfloat(app.p(title), app.p(message), 
                initialvalue = initialvalue, minvalue = minvalue, maxvalue = maxvalue, parent = app.root_window)

def askinteger(app, title = 'Input float value', message = 'Input float value',
                        initialvalue = None, minvalue = None, maxvalue = None):
        return tkinter.simpledialog.askinteger(app.p(title), app.p(message), 
                initialvalue = initialvalue, minvalue = minvalue, maxvalue = maxvalue, parent = app.root_window)

def askstring(app, title = 'Input text', message = 'Input float value',
                        initialvalue = None):
        return tkinter.simpledialog.askstring(app.p(title), app.p(message), initialvalue = initialvalue, parent = app.root_window)

def dialog_showinfo(app, title = 'Information', message = 'no message'):
        tkinter.messagebox.showinfo(app.p(title), message, parent = app.root_window)

def dialog_showwarning(app, title = 'Warning', message = 'no message'):
        tkinter.messagebox.showwarning(app.p(title), app.p(message), parent = app.root_window)

def dialog_showerror(app, title = 'Error', message = 'no message'):
        tkinter.messagebox.showerror(app.p(title), app.p(message), parent = app.root_window)
        
def dialog_retrycancel(app, title = 'Retry?', message = 'Do you want to retry?'):
        return tkinter.messagebox.askretrycancel(app.p(title), app.p(message), parent = app.root_window)

def dialog_yesno(app, title = 'Quit', message = 'Do you really quit the program?'):
#        root = tkinter.Tk()
        root = app.root_window
#        print("root=", root)
        return tkinter.messagebox.askyesno(app.p(title), app.p(message), parent = root)

def dialog_okcancel(app, title = 'Quit', message = 'Do you really quit the program?'):
        return tkinter.messagebox.askokcancel(app.p(title), app.p(message), parent = app.root_window)

def Menu(app, parent = None, **kwargs):
    return tkinter.Menu(parent, **kwargs)


def Frame(app, parent = None, **kwargs):
    return tkinter.Frame(parent, **kwargs)

def CheckButton(app, parent = None, variable = None, defvalue = False, command = None,
                onvalue = True, offvalue = False, **kwargs):
    if variable is None:
        variable = tkinter.BooleanVar(value = defvalue)

# イベントハンドラーでget()しないと正常に初期化されない
    def checked_button():
        variable.get()

    if command is None:
        command = checked_button

    check = tkinter.Checkbutton(parent, variable = variable, command = command, 
                onvalue = onvalue, offvalue = offvalue, **kwargs)
    check.variable = variable

    return check
    
def Entry(app, parent = None, **kwargs):
    return tkinter.Entry(parent, **kwargs)

def Combobox(app, parent = None, **kwargs):
    return tkinter.ttk.Combobox(parent, **kwargs)

def Button(app, parent = None, **kwargs):
    caption = kwargs.get('text', "")
    kwargs['text'] = app.p(caption)
#    return tkinter.Button(parent, **kwargs)
    return tkButton(parent, app, **kwargs)

def Text(app, parent = None, **kwargs):
    return tkinter.Text(parent, **kwargs)

def Canvas(app, parent = None, canvas_args = {}, **kwargs):
#    use_scrollbar = canvas_args.get('use_scrollbar', True)
    use_scrollbar   = canvas_args.get('use_scrollbar', False)
    figsize         = canvas_args.get('figsize', [6, 6])
    min_canvas_size = canvas_args.get('min_canvas_size', [3, 3])

    figure = plt.Figure(figsize = figsize, dpi = 100)
#   figure = plt.subplots()
    app.figure = figure

    frame = tkinter.Frame(parent, relief = tkinter.GROOVE, bg = '#ff0000')

    canvas = FigureCanvasTkAgg(app.figure, master = frame)
    canvas_widget = canvas.get_tk_widget()
    app.canvas        = canvas
    app.canvas_widget = canvas_widget

    def __configure_canvas(event):
        print("resized", event)
        wi = event.width        # frame.winfo_reqwidth()
        hi = event.height       # frame.winfo_reqheight()
        dpi = app.figure.get_dpi()
        wf = event.width / dpi
        hf = event.height / dpi
        size = (max(wi, min_canvas_size[0]), max(hi, min_canvas_size[1]))
        canvas_widget.config(scrollregion = (0, 0, size[0], size[1]))
#            canvas_widget.config(scrollregion=(-100, -100, 800, 600))
#            if interior.winfo_reqwidth()  != canvas_widget.winfo_width() or \
#               interior.winfo_reqheight() != canvas_widget.winfo_height():
        if use_scrollbar:
            print(" canvas size to:", wi, hi)
            print(" fig size fixed to:", *figsize)
            canvas_widget.config(
                width  = wi - vscrollbar.winfo_reqwidth(), 
                height = hi - hscrollbar.winfo_reqheight())
            plt.rcParams["figure.figsize"] = figsize
        else:
            print(" canvas size to:", wi, hi)
            print(" fig size to:", wf, hf)
            canvas_widget.config(width  = wi, height = hi)
            plt.rcParams["figure.figsize"] = [wf, hf]
#           plt.clf()
#           ax.cla()

        canvas.draw()

    def __pickup_point(event):
        print(event.x, event.y, canvas_widget.canvasx(event.x), canvas_widget.canvasy(event.y))

    def __mouse_right_click_on_canvas(event):
        x = canvas_widget.canvasx(event.x)
        y = canvas_widget.canvasy(event.y)
    
        popup_menu = tkinter.Menu(canvas_widget, tearoff = 0)
        popup_menu.add_command(label = 'new')
        popup_menu.add_separator()
        popup_menu.add_command(label = 'update')
        popup_menu.tk_popup(event.x_root,event.y_root,0)

    canvas_widget.bind('<ButtonPress-1>', __pickup_point)
    canvas_widget.bind('<Button-3>', __mouse_right_click_on_canvas)
    frame.bind('<Configure>', __configure_canvas)

    if use_scrollbar:
        vscrollbar = tkinter.ttk.Scrollbar(frame, orient=tkinter.VERTICAL,   command = canvas_widget.xview)
        hscrollbar = tkinter.ttk.Scrollbar(frame, orient=tkinter.HORIZONTAL, command = canvas_widget.yview)

        vscrollbar.grid(row = 0, column = 1, stick = tkinter.N + tkinter.S)
        hscrollbar.grid(row = 1, column = 0, stick = tkinter.W + tkinter.E)
        canvas_widget.grid(row = 0, column = 0, stick = tkinter.N + tkinter.S + tkinter.E + tkinter.W)#, expand = 1)

        vscrollbar.config(command = canvas_widget.yview)
        hscrollbar.config(command = canvas_widget.xview)
#        canvas_widget['xscrollcommand'] = canvas_h_scroll.set
#        canvas_widget['yscrollcommand'] = canvas_v_scroll.set

        canvas_widget.xview_moveto(0)
        canvas_widget.yview_moveto(0)
        canvas_widget.config(scrollregion=(0, 0, min_canvas_size[0], min_canvas_size[1]))
#        canvas_widget.config(scrollregion=(-100, -100, 800, 800))
    else:
        canvas_widget.pack()
        canvas._tkcanvas.pack()

    frame.pack(fill = tkinter.BOTH, expand = True)

    return canvas

class tkScrolledListbox(tkinter.Listbox):
    def __init__(self, master, **key):
        self.frame = tkinter.Frame(master)
        self.yscroll = tkinter.Scrollbar(self.frame, orient = tkinter.VERTICAL)
        self.yscroll.pack(side = tkinter.RIGHT, fill = tkinter.Y, expand = 1)
        key['yscrollcommand'] = self.yscroll.set
        tkinter.Listbox.__init__(self, self.frame, **key)
        self.yscroll.config(command = self.yview)

        self.pack(side = tkinter.LEFT, fill = tkinter.BOTH, expand = 1)

    def clear(self):
        self.delete(0, tkinter.END)

# Copy geometry methods of self.frame
#        for m in (tkinter.Pack.__dict__.keys() + tkinter.Grid.__dict__.keys() + tkinter.Place.__dict__.keys()):
#            m[0] == '_' or m == 'config' or m == 'configure' or \
#                setattr(self, m, getattr(self.frame, m))

#class tkEntry(tkinter.Entry):
class tkEntry(tkinter.ttk.Entry):
    def __init__(self, parent = None, app = None, **kwargs):
        self.app = app
        self.default_fontfamily = "" #"Yu Gothic UI"
        self.default_fontsize = 10

        super().__init__(parent, **kwargs)
        self.__create_menu()
        self.__bind_event()

    def __create_menu(self):
        font = (self.default_fontfamily, self.default_fontsize)
        self.menu = tkinter.Menu(self.app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee", 
                                    activebackground = "#000000", activeforeground = "#ffffff")
        self.menu.add_command(label = "Cut",    command = self.__on_cut,    font = font)
        self.menu.add_command(label = "Copy",   command = self.__on_copy,   font = font)
        self.menu.add_command(label = "Paste",  command = self.__on_paste,  font = font)
        self.menu.add_command(label = "Delete", command = self.__on_delete, font = font)
        self.menu.add_separator()
        self.menu.add_command(label = "Select all", command = self.__on_select_all, font = font)


    def __on_cut(self):
        self.event_generate("<<Cut>>")

    def __on_copy(self):
        self.event_generate("<<Copy>>")

    def __on_paste(self):
        self.event_generate("<<Paste>>")

    def __on_delete(self):
        first = self.index(tkinter.SEL_FIRST)
        last  = self.index(tkinter.SEL_LAST)
#       first = self.index("sel.first")
#       last = self.index("sel.last")
        self.delete(first, last)

    def __on_select_all(self):
        self.select_range(0, "end")

    def __bind_event(self):
        self.bind("<Button-3>", self.__do_popup)

    def __do_popup(self, e):
        try:
            self.menu.tk_popup(e.x_root, e.y_root)
        finally:
            self.menu.grab_release()

class tkSpinbox(tkinter.Spinbox):
    def __init__(self, parent = None, app = None, **kwargs):
        self.app = app
        self.default_fontfamily = "Yu Gothic UI"
        self.default_fontsize = 10

        super().__init__(parent, **kwargs)
        self.__create_menu()
        self.__bind_event()

    def __create_menu(self):
        font = (self.default_fontfamily, self.default_fontsize)
        self.menu = tkinter.Menu(self.app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee", 
                                    activebackground = "#000000", activeforeground = "#ffffff")
        self.menu.add_command(label = "Cut",    command = self.__on_cut,    font = font)
        self.menu.add_command(label = "Copy",   command = self.__on_copy,   font = font)
        self.menu.add_command(label = "Paste",  command = self.__on_paste,  font = font)
        self.menu.add_command(label = "Delete", command = self.__on_delete, font = font)
        self.menu.add_separator()
        self.menu.add_command(label = "Select all", command = self.__on_select_all, font = font)

    def __on_cut(self):
        self.event_generate("<<Cut>>")

    def __on_copy(self):
        self.event_generate("<<Copy>>")

    def __on_paste(self):
        self.event_generate("<<Paste>>")

    def __on_delete(self):
        first = self.index(tkinter.SEL_FIRST)
        last  = self.index(tkinter.SEL_LAST)
#       first = self.index("sel.first")
#       last = self.index("sel.last")
        self.delete(first, last)

    def __on_select_all(self):
        self.select_range(0, "end")

    def __bind_event(self):
        self.bind("<Button-3>", self.__do_popup)

    def __do_popup(self, e):
        try:
            self.menu.tk_popup(e.x_root, e.y_root)
        finally:
            self.menu.grab_release()

def RangeFrame(app, parent = None, 
               frame_args = {},
               head_label_args   = {},   
               x0_entry_args     = {}, 
               center_label_args = {}, 
               x1_entry_args     = {},
               gridframe         = None,
              igridrow           = None):

        if gridframe:
            frame = gridframe
        else:
            frame = tkinter.Frame(parent, **frame_args)

        head_label = tkinter.Label(frame, **head_label_args)
        head_label.grid(row = igridrow, column = 0, sticky = tkinter.W)

        if 'textvariable' in x0_entry_args:
            if type(x0_entry_args['textvariable']) is list:
                x0_entry_args['textvariable'] \
                        = set_variable_from_list(app, 
                                x0_entry_args['textvariable'][0], x0_entry_args['textvariable'][1])
#        x0_entry = ttk.Entry(frame, **x0_entry_args)
        x0_entry = tkSpinbox(parent = frame, app = app, **x0_entry_args)
#        x0_entry = tkinter.Spinbox(frame, **x0_entry_args)
        x0_entry.grid(row = igridrow, column = 1)

        center_label = tkinter.Label(frame, **center_label_args)
        center_label.grid(row = igridrow, column = 2, sticky = tkinter.E)

        if 'textvariable' in x1_entry_args:
            if type(x1_entry_args['textvariable']) is list:
                x1_entry_args['textvariable'] \
                        = set_variable_from_list(app,
                                    x1_entry_args['textvariable'][0], x1_entry_args['textvariable'][1])
#        x1_entry = ttk.Entry(frame, **x1_entry_args)
        x1_entry = tkSpinbox(parent = frame, app = app, **x1_entry_args)
#        x1_entry = tkinter.Spinbox(frame, **x1_entry_args)
        x1_entry.grid(row = igridrow, column = 3)
    
        return frame

def ComboboxFrame(app, parent,
        frame_args      = { 'bg': 'dim gray' },
        head_label_args = { 'text': 'Input module:' },
        combobox_args   = { 'textvariable': ['varname', None], 'values': ['abc', 'def'], 'default': 'def', 'width': 10 },
        tail_label_args = { 'text': '' },
        gridframe         = None,
        igridrow          = None,
        is_print          = False,
        **kwargs
        ):
    tkvars = app.tkvars
    if gridframe and igridrow is not None:
        frame = gridframe
    else:
        frame = tkinter.Frame(parent, **frame_args)

    if head_label_args:
        label = tkinter.Label(frame, **head_label_args)
        if igridrow is not None:
            label.grid(row = igridrow, column = 0)
        else:
            label.pack(side = tkinter.LEFT)

    if combobox_args:
        if 'textvariable' in combobox_args:
            if type(combobox_args['textvariable']) is list:
                key = combobox_args['textvariable'][0]
                var = combobox_args['textvariable'][1]
                combobox_args['textvariable'] = set_variable_from_list(app, key, var)
                tkvars.set_attr(key, var)
            else:
                var = combobx_args['textvariable']

        cb = tkinter.ttk.Combobox(frame, textvariable = var, 
                    values = combobox_args['values'], width = combobox_args['width'])
        cb.set(combobox_args['default'])
        cb.bind('<<ComboboxSelected>>', lambda e: print('var=%s' % var.get()))
        if igridrow is not None:
            cb.grid(row = igridrow, column = 1)
        else:
            cb.pack(side = tkinter.LEFT, anchor = 'w', padx = 2, pady = 2) #, expand = True, fill = tkinter.X)

        """
        var = tkinter.StringVar(value = '')
        cb = tkinter.ttk.Combobox(combobox_frame, textvariable = var, 
                    values = combobox_args['values'], width = combobox_args['width'])
        cb.set(combobox_args['default'])
        cb.bind('<<ComboboxSelected>>', lambda e: print('var=%s' % var.get()))
        cb.pack(side = tkinter.LEFT, expand = True, fill = tkinter.X)
        combobox_frame.pack(side = tkinter.LEFT, anchor = 'w', padx = 2, pady = 2)
        """

    if tail_label_args:
        label2 = tkinter.Label(frame, **tail_label_args)
        if igridrow is not None:
            label2.grid(row = igridrow, column = 0)
        else:
            label2.pack(side = tkinter.LEFT)

    frame.pack(side = tkinter.TOP, anchor = tkinter.W, padx = 2, pady = 2)

    return frame

def PathFrame(app, parent = None, 
        frame_args         = {},
        head_label_args    = None,
        entry_args         = None,
        combobox_args      = None,
        spinbox_args       = None,
        tail_label_args    = None,
        button_args        = None,
        save_button_args   = None,
        edit_button_args   = None,
        copy_button_args   = None,
        optionmenu_args    = None,
        menubotton_args    = None,
        shell_button_args  = None,
        font_size          = 10, 
        gridframe          = None,
        grid_args          = {},
        igridrow           = None,
        is_print           = False,
        **kwargs):
    tkvars = app.tkvars
    if gridframe and igridrow is not None:
        frame = gridframe
    else:
        frame = tkinter.Frame(parent, **frame_args)
    
    if head_label_args and head_label_args.get("width", 1) != 0:
        head_label_args['text'] = app.p(head_label_args['text'])
#        print("\nhead_label_args=", head_label_args, "\n")
        label = tkinter.Label(frame, **head_label_args)
#            label = tk.Label(frame, text = head_text_args.get('text', ''))

        if 'igridcol' in head_label_args.keys():
            igridcol = head_label_args['igridcol']
            del head_label_args["igridcol"]
        else:
            igridcol = 0

        if igridrow is not None:
            label.grid(row = igridrow, column = igridcol, **grid_args)
        else:
            label.pack(side = 'left')

    if entry_args and entry_args.get("width", 0) != 0:
        if 'textvariable' in entry_args:
            if type(entry_args['textvariable']) is list:
                key = entry_args['textvariable'][0]
                val = entry_args['textvariable'][1]
                entry_args['textvariable'] = set_variable_from_list(app, key, val)
                tkvars.set_attr(key, val)

#        entry = tkinter.ttk.Entry(frame, **entry_args)
        entry = tkEntry(parent = frame, app = app, **entry_args)

        if 'igridcol' in entry_args.keys():
            igridcol = entry_args['igridcol']
            del entry_args["igridcol"]
        else:
            igridcol = 1
        if igridrow is not None:
            entry.grid(row = igridrow, column = igridcol)
        else:
            entry.pack(side = 'left', expand = True, fill = tkinter.X)

        frame.entry = entry

    if combobox_args and combobox_args.get("width", 0) != 0:
        if 'textvariable' in combobox_args:
            if type(combobox_args['textvariable']) is list:
                key = combobox_args['textvariable'][0]
                val = combobox_args['textvariable'][1]
                combobox_args['textvariable'] = set_variable_from_list(app, key, val)
                tkvars.set_attr(key, val)

        entry = tkinter.ttk.Combobox(frame, **combobox_args)

        if 'igridcol' in combobox_args.keys():
            igridcol = combobox_args['igridcol']
            del combobox_args["igridcol"]
        else:
            igridcol = 1
        if igridrow is not None:
            entry.grid(row = igridrow, column = igridcol)
        else:
            entry.pack(side = 'left', expand = True, fill = tkinter.X)

        frame.combobox = entry

    if spinbox_args and spinbox_args.get("width", 0) != 0:
        if 'textvariable' in spinbox_args:
            if type(spinbox_args['textvariable']) is list:
                key = spinbox_args['textvariable'][0]
                val = spinbox_args['textvariable'][1]
                spinbox_args['textvariable'] = set_variable_from_list(app, key, val)
                tkvars.set_attr(key, val)

        entry = tkSpinbox(parent = frame, app = app, **spinbox_args)
#        entry = tkinter.Spinbox(frame, **spinbox_args)

        if 'igridcol' in spinbox_args.keys():
            igridcol = spinbox_args['igridcol']
            del spinbox_args["igridcol"]
        else:
            igridcol = 1
        if igridrow is not None:
            entry.grid(row = igridrow, column = igridcol)
        else:
            entry.pack(side = 'left', expand = True, fill = tkinter.X)

        frame.spinbox = entry

    if tail_label_args and tail_label_args.get("width", 0) != 0:
        tail_label_args['text'] = app.p(tail_label_args['text'])
        label2 = tkinter.ttk.Label(frame, **tail_label_args)
#        label2 = ttk.Label(frame, text = tail_text_args.get('text', ''))

        if 'igridcol' in tail_label_args.keys():
            igridcol = tail_label_args['igridcol']
            del tail_label_args["igridcol"]
        else:
            igridcol = 2
        if igridrow is not None:
            label2.grid(row = igridrow, column = igridcol)
        else:
            label2.pack(side = 'left')

        if kwargs.get('ini_dir', None) is None:
            kwargs['ini_dir'] = '.'
        if kwargs.get('file_type', None) is None:
            kwargs['file_type'] = [('All', '*.*')]

    if button_args and button_args.get("width", 0) != 0:
        button_args['text'] = app.p(button_args['text'])
        if button_args.get('command', None) is None:
            button_args['command'] = lambda: path_button_click(app, entry_args['textvariable'])
    
        if 'igridcol' in button_args.keys():
            igridcol = button_args['igridcol']
            del button_args["igridcol"]
        else:
            igridcol = 3
        button = Button(app, frame, **button_args)
#        button = tkinter.Button(frame, **button_args)
        if igridrow is not None:
            button.grid(row = igridrow, column = igridcol)
        else:
            button.pack(side = 'left')

        frame.button = button

    if save_button_args and save_button_args.get("width", 0) != 0:
        save_button_args['text'] = app.p(save_button_args['text'])
        if save_button_args.get('command', None) is None:
            save_button_args['command'] = lambda: path_button_click(app, entry_args['textvariable'])

        if 'igridcol' in save_button_args.keys():
            igridcol = save_button_args['igridcol']
            del save_button_args["igridcol"]
        else:
            igridcol = 4
        button = Button(app, frame, **save_button_args)
#        button = tkinter.Button(frame, **save_button_args)
        if igridrow is not None:
            button.grid(row = igridrow, column = igridcol)
        else:
            button.pack(side = 'left')

        frame.save_button = save_button
        
    if edit_button_args and edit_button_args.get("width", 0) != 0:
        edit_button_args['text'] = app.p(edit_button_args['text'])
        if edit_button_args.get('command', None) is None:
            edit_button_args['command'] \
                = lambda: edit_button_click(app, entry_args['textvariable'])
     
        edit_button = Button(app, frame, **edit_button_args)
#        edit_button = tkinter.Button(frame, **edit_button_args)
        if igridrow is not None:
            edit_button.grid(row = igridrow, column = 4)
        else:
            edit_button.pack(side = tkinter.LEFT)

        frame.edit_button = edit_button

    def copy_to_clipboard(parent, entry_variable):
        print(f"copy [{entry_variable.get()}] to clipboard")
        parent.clipboard_clear()
        parent.clipboard_append(entry_variable.get())
        parent.update_idletasks()

    if copy_button_args:
        copy_button_args['text'] = app.p(copy_button_args['text'])
        if copy_button_args.get('command', None) is None:
            copy_button_args['command'] \
                = lambda: copy_to_clipboard(frame, entry_args['textvariable'])

        if 'igridcol' in copy_button_args.keys():
            igridcol = copy_button_args['igridcol']
            del copy_button_args["igridcol"]
        else:
            igridcol = 3
        button = Button(app, frame, **copy_button_args)
        if igridrow is not None:
            button.grid(row = igridrow, column = igridcol)
        else:
            button.pack(side = 'left')

        frame.copy_button = button

    if optionmenu_args and optionmenu_args.get("width", 0) != 0:
        if type(optionmenu_args['textvariable']) is list:
            key = optionmenu_args['textvariable'][0]
            val = optionmenu_args['textvariable'][1]
            optionmenu_args['textvariable'] = set_variable_from_list(app, key, val)
            tkvars.set_attr(key, val)
        if optionmenu_args.get('command', None) is None:
           optionmenu_args['command'] \
                = lambda: execute_command(app, 
                        optionmenu_args['textvariable'].get(), entry_args['textvariable'].get(),
                        is_print = is_print)

        def optionmenu_callback(*args):
             optionmenu_args['command']()

        values = optionmenu_args.get('values', ['notepad.exe'])
        optionmenu = tkinter.OptionMenu(frame, optionmenu_args['textvariable'], *values)
        optionmenu.configure(width = optionmenu_args.get('width', None))
        optionmenu.configure(height = optionmenu_args.get('height', 1))
        optionmenu.configure(font = optionmenu_args.get('font', None))
        optionmenu_args['textvariable'].trace("w", optionmenu_callback)
#        optionmenu.configure(command = optionmenu_args['command'])
        if igridrow is not None:
            optionmenu.grid(row = igridrow, column = 5)
        else:
            optionmenu.pack(side = tkinter.LEFT)

        frame.optionmenu = optionmenu

# Menubottun does not work in frame
    if menubotton_args and menubotton_args.get("width", 0) != 0:
#        if menubotton_args.get('command', None) is None:
#            menubotton_args['command'] \
#                = lambda: optionmenu_selected(app, optionmenu_args['textvariable'])

        menubotton = tkinter.Menubutton(frame, **menubotton_args)
        menu = tkinter.Menu(menubotton)
        menu.add_command(label="A")
        menu.add_command(label="B")
        menubotton["menu"] = menu
        if igridrow is not None:
            menubotton.grid(row = igridrow, column = 6)
        else:
            menubotton.pack(side = tkinter.LEFT)

        frame.menubotton = menubotton

    if shell_button_args and shell_button_args.get("width", 0) != 0:
        shell_button_args['text'] = app.p(shell_button_args['text'])
        if shell_button_args.get('command', None) is None:
            shell_button_args['command'] \
                = lambda: shell_button_click(app)
    
        shell_button = Button(app, frame, **shell_button_args)
#        shell_button = tkinter.Button(frame, **shell_button_args)
        if igridrow is not None:
            shell_button.grid(row = igridrow, column = 7)
        else:
            shell_button.pack(side = tkinter.LEFT)
            
        frame.shell_button = shell_button

    frame.pack(side = tkinter.TOP, anchor = tkinter.W, padx = 2, pady = 2, expand = True, fill = tkinter.X)

    return frame

def LabelEntryFrame(app, parent = None, 
                    frame_args    = {},
                    label_args    = None,
                    entry_args    = None,
                    combobox_args = None,
                    spinbox_args  = None,
                    gridframe     = None,
                    grid_args     = {},
                    igridrow      = 0):
    return PathFrame(app, parent = parent, 
                    frame_args      = frame_args, 
                    head_label_args = label_args, 
                    entry_args      = entry_args, 
                    combobox_args   = combobox_args, 
                    spinbox_args    = spinbox_args, 
                    gridframe       = gridframe, 
                    grid_args       = grid_args,
                    igridrow        = igridrow)

def make_check_box(app, cparams, tkvars, parent, grid_frame,
                    varname = 'none', 
                    label = None, 
                    values = None,
                    defval    = True,  # Used to initialize entry
                    reset_val = True,  # Used for 'set_default()'
                    help_text    = None,
                    button1_args = None, 
                    button2_args = None,
                    onvalue = True,
                    offvalue = False,
                    igridrow = 0):
        def on_clicked():
#            print("changed to ", tkvars[varname].get())
            pass

        def set_default():
            tkvars[varname].set(reset_val)

        def show_help(help_text):
            help_text = app.p(help_text)
            return dialog_okcancel(app, title = f'Help for {varname}', message = help_text)

        if label is None:
            label = varname

        if help_text is None:
            help_text = f"help_{varname}"

        frame = grid_frame #tkinter.Frame(parent)
        
        if type(onvalue) is int:
            tkvars[varname] = tkinter.IntVar(value = cparams.get(varname, defval))
        else:
            tkvars[varname] = tkinter.BooleanVar(value = cparams.get(varname, defval))
        check = CheckButton(app, parent = frame, variable = tkvars[varname], text = label,
                            onvalue = onvalue, offvalue = offvalue,
#                            command = on_clicked)
                            command = None)

        igridcol = 0
        if igridrow is not None:
            check.grid(row = igridrow, column = igridcol)
            igridcol += 1
        else:
            check.pack(side = 'left')

        if button1_args:
            if button1_args["command"] == 'set_default':
                button1_args["command"] = lambda: set_default()
            elif button1_args["command"] == 'show_help':
                button1_args["command"] = lambda: show_help(help_text)

            if 'igridcol' in button1_args.keys():
                igridcol = button1_args['igridcol']
                del button1_args["igridcol"]
            else:
                igridcol = 2

            button1_args['text'] = button1_args.get('text', '')
            button = Button(app, frame, **button1_args)
#            button1_args['text'] = app.p(button1_args.get('text', ''))
#            button = tkinter.Button(frame, **button1_args)
            if igridrow is not None:
                button.grid(row = igridrow, column = igridcol)
            else:
                button.pack(side = 'left')

        if button2_args:
            button2_args['text'] = app.p(button2_args.get('text', ''))
            if button2_args["command"] == 'set_default':
                button2_args["command"] = lambda: set_default()
            elif button2_args["command"] == 'show_help':
                button2_args["command"] = lambda: show_help(help_text)

            if 'igridcol' in button2_args.keys():
                igridcol = button2_args['igridcol']
                del button2_args["igridcol"]
            else:
                igridcol = 3

            button = Button(app, frame, **button2_args)
#            button = tkinter.Button(frame, **button2_args)
            if igridrow is not None:
                button.grid(row = igridrow, column = igridcol)
            else:
                button.pack(side = 'left')

#        frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)

def make_var_box(app, cparams, tkvars, parent, grid_frame,
                    varname = 'none', vartype = 'int', 
                    label = None, 
                    values = None,
                    from_ = None, to = 100, increment = 10, box_width = 6, 
                    defval    = 0,     # Used to initialize entry
                    reset_val = None,  # Used for 'set_default()'
                    help_text    = None,
                    button1_args = None, 
                    button2_args = None,
                    igridrow = 0):
        if label is None:
            label = varname

        if type(defval) is list or type(defval) is tuple:
            config  = defval[0]
            _defval = defval[1]
            defval = config.get(varname, _defval)

        if help_text is None:
            help_text = f"help_{varname}"
            
        if vartype == 'int':
            tkvars[varname] = tkinter.IntVar(value = cparams.get(varname, defval))
            textbox_args = {'spinbox_args':
                            {'textvariable': [varname, tkvars[varname]], 'width': box_width, 
                             'from': from_, 'to': to, 'increment': increment}
                       }
        elif vartype == 'str':
            tkvars[varname] = tkinter.StringVar(value = cparams.get(varname, defval))
            textbox_args = {'entry_args': 
                            {'textvariable': [varname, tkvars[varname]], 'width': box_width}
                       } 
        elif vartype == 'double' or vartype == 'float':
            tkvars[varname] = tkinter.DoubleVar(value = cparams.get(varname, defval))
            if from_ is None:
                textbox_args = {'entry_args': 
                                {'textvariable': [varname, tkvars[varname]], 'width': box_width}
                           } 
            else:
                textbox_args = {'spinbox_args': 
                            {'textvariable': [varname, tkvars[varname]], 'width': box_width, 
                             'from': from_, 'to': to, 'increment': increment}
                           } 

        if values is None:
            frame = LabelEntryFrame(app, parent,
                frame_args = {'bg': 'white'},
                label_args = {'text': f'{label}:', 'anchor': tkinter.E},
#                entry_args = {'textvariable': ['score_mode', tkvars["score_mode"]], 'width': box_width},
#                            combobox_args = None, 
#                spinbox_args = {'textvariable': [varname, tkvars[varname]], 'width': box_width, 
#                            'from': from_, 'to': to, 'increment': increment},
                **textbox_args,
                gridframe = grid_frame,
                grid_args = { "sticky": tkinter.E },
                igridrow  = igridrow
                )
        elif from_ is not None:
            frame = PathFrame(app, parent,
                frame_args = {'bg': 'white'},
                head_label_args = {'text': f'{label}:', 'anchor': tkinter.E},
                entry_args = {'textvariable': [varname, tkvars[varname]], 'width': 6},
                gridframe = grid_frame,
                grid_args = { "sticky": tkinter.E },
                igridrow  = igridrow
                )
        else:
            frame = PathFrame(app, parent,
                frame_args = {'bg': 'white'},
                head_label_args = {'text': f'{label}:', 'anchor': tkinter.E},
                combobox_args = {'textvariable': [varname, tkvars[varname]], 
                                 'values': values,
                                 'width': 6},
                gridframe = grid_frame,
                grid_args = { "sticky": tkinter.E },
                igridrow  = igridrow
                )

        def set_default():
            tkvars[varname].set(reset_val)

        def show_help(help_text):
            help_text = app.p(help_text)
            return dialog_okcancel(app, title = f'Help for {varname}', message = help_text)

        if button1_args:
            if button1_args["command"] == 'set_default':
                button1_args["command"] = lambda: set_default()
            elif button1_args["command"] == 'show_help':
                button1_args["command"] = lambda: show_help(help_text)

            if 'igridcol' in button1_args.keys():
                igridcol = button1_args['igridcol']
                del button1_args["igridcol"]
            else:
                igridcol = 2

            button1_args['text'] = button1_args.get('text', '')
            button = Button(app, frame, **button1_args)
#            button1_args['text'] = app.p(button1_args.get('text', ''))
#            button = tkinter.Button(frame, **button1_args)
            if igridrow is not None:
                button.grid(row = igridrow, column = igridcol)
            else:
                button.pack(side = 'left')

        if button2_args:
            button2_args['text'] = app.p(button2_args.get('text', ''))
            if button2_args["command"] == 'set_default':
                button2_args["command"] = lambda: set_default()
            elif button2_args["command"] == 'show_help':
                button2_args["command"] = lambda: show_help(help_text)

            if 'igridcol' in button2_args.keys():
                igridcol = button2_args['igridcol']
                del button2_args["igridcol"]
            else:
                igridcol = 3

            button = Button(app, frame, **button2_args)
#            button = tkinter.Button(frame, **button2_args)
            if igridrow is not None:
                button.grid(row = igridrow, column = igridcol)
            else:
                button.pack(side = 'left')

        frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)

# lamda関数を呼び出す際には、呼び出された時の変数の内容が反映される。
# 同じスコープで同じ変数でlamda関数を呼び出すと、最後に変数に代入された内容が反映されてしまう
# これを避けるため、lamda関数につかう変数は、局所的なスコープで１回のみ使う
# この関数は、lamda関数に使っている変数を局所化するために作った
def make_path_frame(app, cparams, parent, grid_frame, tkvar, varname, 
                file_type = [("All", "*.*")], working_dir = '.', 
                open_command = None, post_open_command = None, 
                head_label_width = 10,
                entry_width = 60,
                button_width = 12,
                button_args  = None,
                copy_button_args  = None,
                edit_button_width = 6,
                edit_button_args = None,
                shell_button_width = 2,
                optionmenu_width = 3,
                font_size = 10,
                is_print = True, igridrow = 0):
        config  = app.configparams
        ctkvars = app.tkvars
        tkvars  = ctkvars.get_param_dict()

        applications = app.get_external_apps([config.get("editor_path", None), config.get("vesta_path", None)])

        varname_app = f'{varname}_app'
        tkvars[varname]     = tkinter.StringVar(value = cparams.get(varname, config.get(varname, "")))
        tkvars[varname_app] = tkinter.StringVar(value = 'app')

        if working_dir == '.':
            path = tkvar[varname].get()
            if path != '':
                dirname, basename, filebody, ext = split_file_path(path)
                working_dir  = dirname

        def button_click_func(app, cparams, entry, file_type = None, ini_dir = None):
            if open_command:
                open_command(app, cparams, file_type, ini_dir)
            else:
#                path_button_click(app, cparams, entry, file_type = file_type, ini_dir = ini_dir)
                path_button_click(app, entry, file_type = file_type, ini_dir = ini_dir)
            if post_open_command:
                post_open_command(app, cparams)
        
        if button_args is None:
            button_args = {'text': 'open', "width": button_width, 'font': ("", font_size), 
                           'command': lambda: button_click_func(app, cparams, tkvars[varname],
                                               file_type = file_type, ini_dir = working_dir)
                          }

        if edit_button_args is None:
            edit_button_args  = {'text': 'ed', "width": edit_button_width, 'font': ("", font_size)}
        
        frame = PathFrame(app, parent,
            frame_args        = {'bg': 'white'},
            head_label_args   = {'text': f'{varname}:', 'width': head_label_width, 'anchor': tkinter.E, 'font': ("", font_size)},
            entry_args        = {'textvariable': [varname, tkvars[varname]], 'width': entry_width, 'font': ("", font_size)},
            button_args       = button_args,
            edit_button_args  = edit_button_args,
            copy_button_args  = copy_button_args,
            optionmenu_args   = {'textvariable': [varname_app, tkvars[varname_app]],
                                 "width": optionmenu_width, 'font': ("", font_size),
                                 'values': applications, 'anchor': tkinter.RIGHT,
                                 'font': ("", font_size),
                                 "command": lambda: execute_command(
                                            app, 
                                            working_dir = working_dir, is_print = is_print,
                                            command = tkvars[varname_app].get(), 
                                            files = [tkvars[varname].get()])
                                },
            shell_button_args = {'text': 'sh', "width": shell_button_width, 'font': ("", font_size)},
            font_size = font_size,

            gridframe = grid_frame,
            grid_args = { "sticky": tkinter.E },
            igridrow = igridrow
            )
#      program_path_frame.pack(side = tkinter.TOP, anchor = 'w', padx = 2, pady = 2)

def Editor(app, parent = None, frame_args = {},
                    head_label_args  = {}, 
                    entry_args       = {},
                    tail_label_args  = None, 
                    button_args      = {},
                    eval_button_args = None,
                    **kwargs):
    frame = tkinter.Frame(parent, **frame_args)

    path_frame = PathFrame(app, parent = frame, frame_args = frame_args,
                        head_label_args   = head_label_args, 
                        entry_args        = entry_args,
                        tail_label_args   = tail_label_args, 
                        button_args       = button_args,
                        shell_button_args = eval_button_args,
                        **kwargs)
    path_frame.pack(side = tkinter.TOP, anchor = 'w')

    text_frame = tkinter.Text(frame, **kwargs)
    text_frame.pack(side = tkinter.TOP, anchor = 'w')

    frame.PathFrame   = path_frame
    frame.entry       = path_frame.__dict__.get("entry", None)
    frame.eval_button = path_frame.__dict__.get("shell_button", None)
    frame.Text        = text_frame

    def __on_cut(self):
        self.event_generate("<<Cut>>")

    def __on_copy(self):
        self.event_generate("<<Copy>>")

    def __on_paste(self):
        self.event_generate("<<Paste>>")

    def __on_delete(self):
        first = self.index(tkinter.SEL_FIRST)
        last  = self.index(tkinter.SEL_LAST)
#       first = self.index("sel.first")
#       last = self.index("sel.last")
        self.delete(first, last)

    def __on_select_all(self):
        self.select_range(0, "end")

    def __do_popup(self, e):
        try:
            self.menu.tk_popup(e.x_root, e.y_root)
        finally:
            self.menu.grab_release()

    def __create_menu(parent):
        font = ('', 10)
        parent.menu = tkinter.Menu(app.root_window, tearoff = 0, background = "#111111", foreground = "#eeeeee", 
                                    activebackground = "#000000", activeforeground = "#ffffff")
        parent.menu.add_command(label = "Cut",    command = lambda: __on_cut(parent),    font = font)
        parent.menu.add_command(label = "Copy",   command = lambda: __on_copy(parent),   font = font)
        parent.menu.add_command(label = "Paste",  command = lambda: __on_paste(parent),  font = font)
        parent.menu.add_command(label = "Delete", command = lambda: __on_delete(parent), font = font)
        parent.menu.add_separator()
        parent.menu.add_command(label = "Select all", command = lambda: __on_select_all(parent), font = font)

    __create_menu(text_frame)
    text_frame.bind("<Button-3>", lambda e: __do_popup(text_frame, e))

    return frame

class tkButton(tkinter.Button):
    def __init__(self, master, app, **kwargs):
        super().__init__(master, **kwargs)
        self.app = app
        self.default_font = kwargs.get('font', 10)

    def set_text(self, caption):
        if caption is None:
            caption = ''
            
        m = re.match(r'\!\!\!\s+(\S.*)', caption)
        if m:
            caption = m.groups()[0]
            self.configure(font = ("", self.default_font[1], "normal", "roman", "normal", "overstrike"), fg = "red")
        else:
            self.configure(font = ("", self.default_font[1]), fg = "black")
#            self.configure(font = ("", 8, "normal", "roman", "normal"), fg = "black")

        self.configure(text = caption)
        if self.app:
            caption = self.app.p(caption)

        self.configure(text = caption)

    def get_text(self):
        return self["text"]

class tkSetupDialog(tkinter.simpledialog.Dialog):
    def __init__(self, master, app = None, title = None, 
                entry_width = 30, button_width = 4, edit_button_width = 2, shell_button_width = 2,
                widgets = 'enditor_path|confirm_on_exit|debug_mode',
                font_size = 10,
                is_print = False) -> None:
        self.app = app
        self.entry_width = entry_width
        self.button_width = button_width
        self.edit_button_width = edit_button_width
        self.shell_button_width = shell_button_width
        self.widgets = widgets
        self.font_size = font_size
        self.is_print = is_print

        config  = self.app.configparams
        config.editor_path = config.get('editor_path', '')
        for prg in ['code', 'notepad.exe', 'vim', 'vi', 'xemacs', 'emacs']:
            if config.editor_path == '':
                config.editor_path = find_executable_path(prg, defval = '')

        config.confirm_on_exit = config.get('confirm_on_exit', True)
#        print("tkSetupDialog.__init__: confirm_on_exit:", config.confirm_on_exit)
        config.confirm_on_exit = config.get('confirm_on_exit', True)

        if self.is_print:
            print("editor_path: ", config.editor_path)

        super().__init__(parent = master, title = title)

    def body(self, master) -> None:
        cparams = self.app.get_params()
        tkvars  = self.app.tkvars
        config  = self.app.configparams
        font_size = self.font_size

        if self.app is not None:
            editor_paths  = self.app.get_editors()
            python3_paths = self.app.get_external_apps(["python", "python3"], use_default = False)
            python2_paths = self.app.get_external_apps(["python2", "python2.exe"], use_default = False)
            perl_paths    = self.app.get_external_apps(["perl"], use_default = False)
            vesta_paths   = self.app.get_external_apps(["VESTA"], use_default = False)
            shell_paths   = self.app.get_shells()
            filer_paths   = self.app.get_filers()
            start_paths   = self.app.get_start_apps()
        else:
            editor_paths = []
            python3_paths = []
            python2_paths = []
            perl_paths    = []
            vesta_paths   = []

        def make_path_frame(app, parent, head_label, var_name, var, values,
                            font_size, entry_width, button_width, edit_button_width):
            path_frame = PathFrame(app, parent = parent, 
                frame_args      = {'bg': 'white'},
                head_label_args = {'text': head_label, 'anchor': tkinter.E, 'font': ("", font_size)},
#                entry_args      = {'textvariable': [varname, var], 
#                                   'width': self.entry_width},
                combobox_args      = {'textvariable': [var_name, var], 
                                    'values': values,
                                    'width': entry_width, 'font': ("", font_size)},
                button_args     = {'text': 'path', "width": button_width, 'font': ("", 8),
                                   'command': lambda: path_button_click(self.app, tkvars.editor_path, 
#                                   'command': lambda: path_button_click(self.app, tkvars.editor_path.get(), 
                                      file_type = [('Executables', ";".join(get_file_masks('executables')))], 
                                      ini_dir = '.')
                                  },
                edit_button_args  = {'text': 'run', "width": edit_button_width, 'font': ("", 8),
                                   'command': lambda: execute_command(self.app, 
                                                config.start_path + ' ' + var.get(), self.app.script_path, is_print = self.is_print)
                                  },
                shell_button_args = {'text': 'sh', "width": self.shell_button_width, 'font': ("", 8)},
                igridrow = 0
                )
            return path_frame

        tkvars.editor_path = tkinter.StringVar(value = config.editor_path)
        if 'editor_path' in self.widgets:
            path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'Editor path:', var_name = 'editor_path', var = tkvars.editor_path,
                                values = editor_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.python3_path = tkinter.StringVar(value = config.get("python3_path", ""))
        if 'python3_path' in self.widgets:
            python3_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'python3 path:', var_name = 'python3_path', var = tkvars.python3_path,
                                values = python3_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.python2_path = tkinter.StringVar(value = config.get("python2_path", ""))
        if 'python2_path' in self.widgets:
            python2_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'python2 path:', var_name = 'python2_path', var = tkvars.python2_path,
                                values = python2_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.perl_path = tkinter.StringVar(value = config.get("perl_path", ""))
        if 'perl_path' in self.widgets:
            perl_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'perl path:', var_name = 'perl_path', var = tkvars.perl_path,
                                values = perl_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.start_path = tkinter.StringVar(value = config.get("start_path", ""))
        if 'start_path' in self.widgets:
            start_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'start path:', var_name = 'start_path', var = tkvars.start_path,
                                values = start_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.shell_path = tkinter.StringVar(value = config.get("shell_path", ""))
        if 'shell_path' in self.widgets:
            shell_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'shell path:', var_name = 'shell_path', var = tkvars.shell_path,
                                values = shell_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.filer_path = tkinter.StringVar(value = config.get("filer_path", ""))
        if 'filer_path' in self.widgets:
            filer_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'filer path:', var_name = 'filer_path', var = tkvars.filer_path,
                                values = filer_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        tkvars.vesta_path = tkinter.StringVar(value = config.get("vesta_path", ""))
        if 'vesta_path' in self.widgets:
            vesta_path_frame = make_path_frame(self.app, parent = master, 
                                head_label = 'VESTA path:', var_name = 'vesta_path', var = tkvars.vesta_path,
                                values = vesta_paths, 
                                font_size = font_size, entry_width = self.entry_width,  button_width = self.button_width, 
                                edit_button_width = self.edit_button_width)

        if 'confirm_on_exit' in self.widgets:
            confirm_on_exit_check = CheckButton(self.app, parent = master, text = 'Confirm on exit',
                        defvalue =  config.confirm_on_exit, font = ("", font_size),
                        command = None, onvalue = True, offvalue = False)
            confirm_on_exit_check.pack()
            self.confirm_on_exit_check_var = confirm_on_exit_check.variable

        if 'debug_mode' in self.widgets:
            debug_mode_check = CheckButton(self.app, parent = master, text = 'Debug mode',
                        defvalue =  config.debug, font = ("", font_size),
                        command = None, onvalue = True, offvalue = False)
            debug_mode_check.pack()
            self.debug_mode_check_var = debug_mode_check.variable

        if 'show_log' in self.widgets:
            show_log_check = CheckButton(self.app, parent = master, text = 'Show log on colsole',
                        defvalue =  config.show_log, font = ("", font_size),
                        command = None, onvalue = True, offvalue = False)
            show_log_check.pack()
            self.show_log_check_var = show_log_check.variable

    def buttonbox(self):
        box = tkinter.Frame(self)
#        self.button1 = tkinter.Button(box, text="Apply", width=10, command=self.ok, state=tkinter.DISABLED)
        self.button1 = Button(self.app, box, text="Apply", width=10, command=self.ok)
#        self.button1 = tkinter.Button(box, text="Apply", width=10, command=self.ok)
        self.button1.pack(side=tkinter.LEFT, padx=5, pady=5)
        self.button2 = Button(self.app, box, text="Cancel", width=10, command=self.cancel)
#        self.button2 = tkinter.Button(box, text="Cancel", width=10, command=self.cancel)
        self.button2.pack(side=tkinter.LEFT, padx=5, pady=5) 
        box.pack(fill = tkinter.BOTH, expand = True) 

    def switchButtonState(self):
        if self.button1['state'] == tk.DISABLED:
            self.button1['state'] = tk.NORMAL 

    def apply(self):  # ダイアログボックスを閉じた後
        cparams = self.app.get_params()
        config  = self.app.configparams
        tkvars  = self.app.tkvars

        config.confirm_on_exit = self.confirm_on_exit_check_var.get()
        config.debug           = self.debug_mode_check_var.get()
        config.show_log        = self.show_log_check_var.get()
        self.app.show_log      = config.show_log

        val = tkvars.editor_path.get().strip()
        if val != '':
            config.editor_path = val
            self.app.env["tkEDITOR_PATH"] = val

        val = tkvars.python3_path.get().strip()
        if val != '':
            self.app.python_path = val
            config.python3_path = val

        val = tkvars.python2_path.get().strip()
        if val != '':
            config.python2_path = val

        val = tkvars.perl_path.get().strip()
        if val != '':
            self.app.perl_path = val
            config.perl_path = val

        val = tkvars.start_path.get().strip()
        if val != '':
            config.start_path = val

        val = tkvars.shell_path.get().strip()
        if val != '':
            config.shell_path = val

        val = tkvars.filer_path.get().strip()
        if val != '':
            config.filer_path = val

        val = tkvars.vesta_path.get().strip()
        if val != '':
            config.vesta_path = val

        if self.is_print:
            print("Setup:")
            print("  editor_path:", config.editor_path)
            print("  confirm_on_exit:", config.confirm_on_exit)

def add_widget(self, c):
    app = self.app
    svars = app.svars

    def remove_keys(d, remove_keys):
        d2 = d.copy()
        for key in d.keys():
            if key in remove_keys:
                del d2[key]
        return d2

    def mk_label(config, key):
        s = config.args.get(key, None)
        if s is not None:
            del config.args[key]
            label = tkinter.Label(w, text = s)
            label.pack(side = tkinter.LEFT, anchor = 'w')

    def set_default(def_val, widget_var):
        widget_var.set(def_val)

    def show_help(help_text):
        help_text = app.p(help_text)
        return dialog_okcancel(self.app, title = 'Help', message = help_text)

    def execute(self, app, command):
        c = command.split(';')
        app.print_warning("Execute from custom dialog button:")
        for s in c:
            app.print_warning(f"  [{s}]")
            app.s_engine.execute_a_command(app, app.get_params(), command, fp = None)

    def button_callback(dialog, app, command):
        dialog.update_vars()
        if dialog.callback is not None:
            dialog.callback(self.ret)
        execute(dialog, app, command)

    def add_button(w, text, command, side = 'LEFT', anchor = 'w'):
        if text is not None:
            if command is not None:
                widget = tkinter.Button(w, text = app.p(text),
                           command = lambda: button_callback(self, self.app, command))
                widget.pack(side = side, anchor = anchor)
            else:
                widget = tkinter.Button(w, text = app.p(text))
                widget.pack(side = side, anchor = anchor)

    def make_font_dict(**kwargs):
        d = {}
        for s in ['text', 'fg', 'bg']:
            if kwargs.get(s, None) is not None:
                d[s] = kwargs.get(s, None)
                style = kwargs.get('style', None)
            if style is not None:
                d['font'] = tkinter.font.Font(
#                            family="Menlo",
#                            size=50,
                            weight = style,
#                            slant="italic",
#                            underline=True,
#                            overstrike=True,
                            )
        return d

    frame = self.current_frame
    var_name    = c.var
    tkvar_prev  = self.tkvars.get(var_name, None)
    var_type    = c.args.get("var_type", "str")
    def_val     = c.args.get("def_val", None)
    if def_val is not None:
        show_def_val = True
    else:
        show_def_val = False
        def_val = ""

    init_val = c.args.get("init_val", None)
    if init_val is None:
#       init_val = c.args.get(var_name, None)
        init_val = svars.get(var_name, def_val)

    options = c.vars
    values = c.args.get('values', '').split('##')
    if len(values) >= 1:
        options = values + options
    options = list(dict.fromkeys(options))

    entry_width = c.args.get("entry_width", 30)
    button_text = c.args.get("button_text", "path")
    help_text   = c.args.get("help_text", None) #f"help_{c.var}")
    ini_dir     = c.args.get("initialdir", ".")

    ft = c.args.get("file_type", None)
    if ft is None:
        file_type = [("All", "*.*")]
    else:
        file_type = []
        if '//' in ft:
            _aa = ft.split('//')
        else:
            _aa = [ft]
        for key in _aa:
            if ':' in key:
                try:    
                    name, ext = ft.split(':')
                    file_file.append((name, ext))
                except ValueError as e:  # split()に特有の例外
                    print(f"Warning: ValueError in tktkinter.add_widget(): {e}")
                    print(f"   ft={ft}")
#                    exit()
                except Exception as e:  # その他の例外をキャッチ
                    print(f"Warning: Unexpected error in tktkinter.add_widget(): {e}")
                    print(f"   ft={ft}")
#                    exit()
            else:
                name, ext = "given types", ft
                file_type.append((name, ext))

        file_type.append(("All", "*.*"))

    if 0:
        print(f"{c.widget}")
        print(f"  var_name={var_name}")
        print(f"  var={c.args.get(var_name, 'None')}")
        print(f"  args={c.args}")
        print(f"  vars={c.vars}")
        print(f"  init_val={init_val}")
        print(f"  def_val={def_val}")

# Add a widget group
    for _ in [1]:
            if c.widget == 'dummy':
                pass

            elif c.widget == 'tab':
                if self.__dict__.get('notebook', None) is None or self.notebook is None:
                    self.notebook = tkinter.ttk.Notebook(self.root_frame)
                    self.notebook.pack(expand = True, fill = tkinter.X, side = tkinter.TOP, padx = 20)
#                    self.notebook.pack(expand = True, fill = tkinter.BOTH, side = tkinter.TOP)
                    self.bottom_frame = tkinter.Frame(frame)
                    self.bottom_frame.pack(side = tkinter.TOP)

                self.current_frame = tkinter.Frame(self.notebook)
                frame = self.current_frame
                frame.pack(expand = True, fill = tkinter.X)

                args2 = remove_keys(c.args, [])
                if args2.get('text', None) is not None:
                    args2['text'] += ' '

                self.notebook.add(frame, **args2)

            elif c.widget == 'reset_tab':
                self.current_frame = self.root_frame

            elif c.widget == 'label':
                style = c.args.get('style', 'normal')
                size = c.args.get('size', None)
                font = tkinter.font.Font(
#                    family="Menlo",
#                    size=50,
                    weight = style,
#                    slant="italic",
#                    underline=True,
#                    overstrike=True,
                    )

                args2 = remove_keys(c.args, ["label_head", "label_head_fg", "label_head_style","label_head_bg",
                                             "label_tail", "def_val", "help_text",
                                             "style", "size", "color"])

                w = tkinter.Label(frame, font = font, **args2)
                w.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.X)

            elif c.widget == 'frame':
                self.frame = tkinter.Frame(frame, **c.args)

            elif c.widget == 'pack_frame':
                if self.frame is None:
                    app.print_warning("Error in tktkinter.add_widget(): None frame")
                    continue

#                self.frame.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.BOTH)
                self.frame.pack(side = tkinter.TOP, anchor = 'w')#, expand = True, fill = tkinter.X)
                self.frame = None

            elif c.widget == 'button':
                command = c.args.get("command", "")
                label_head = c.args.get("label_head", None)
                label_tail = c.args.get("label_tail", None)
                if self.frame:
                    w = self.frame
                else:
                    w = tkinter.Frame(frame)

                args2 = remove_keys(c.args, ["command", "help_text", 
                            "label_head", "label_head_fg", "label_head_gg", "label_head_style", "label_tail"])
                text = args2.get('text', None)
                if text is not None:
                    args2['text'] = app.p(text)

                if label_head is not None:
                    d = make_font_dict(text = label_head, fg = c.args.get("label_head_fg", None),
                                          bg = c.args.get("label_head_bg", None), 
                                          style  = c.args.get("label_head_style", None))
                    widget = tkinter.Label(w, **d)
                    widget.pack(side = tkinter.LEFT)

                if command and command != "":
                    widget = tkinter.Button(w, command = lambda: button_callback(self, self.app, command), **args2)
#                    widget = tkinter.Button(w, command = lambda: execute(self, self.app, command), **args2)
                    widget.pack(side = tkinter.LEFT)
                else:
                    widget = tkinter.Button(w, **args2)
                    widget.pack(side = tkinter.LEFT)

                if label_tail is not None:
                    d = make_font_dict(text = label_tail, fg = c.args.get("label_tail_fg", None),
                                          bg = c.args.get("label_tail_bg", None), 
                                          style  = c.args.get("label_tail_style", None))
                    widget = tkinter.Label(w, **d)
                    widget.pack(side = tkinter.LEFT)

                if not self.frame:
                    w.pack(side = tkinter.TOP, anchor = 'w')

            elif c.widget == 'message':
                w = tkinter.Frame(frame)
                family = c.args.get('family', None)
                weight = c.args.get('weight', None)
                size   = c.args.get('size', None)
                args2 = remove_keys(c.args, ["family", "weight", "size"])
                if family is not None or weight is not None or size is not None:
                    def_font = Font(font = "TkDefaultFont")
                    if family is not None:
                        def_font['family'] = faimily
                    if weight is not None:
                        def_font['weight'] = weight
                    if size is not None:
                        def_font['size'] = size
                    args2["font"] = def_font #(def_font['family'], def_font['size'], def_font[')

                widget = tkinter.Message(w, **args2)
                widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)

                w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)

            elif c.widget == 'entry':
                if self.frame:
                    w = self.frame
                else:
                    w = tkinter.Frame(frame)

                side   = c.args.get('side', None)
                anchor = c.args.get('anchor', None)
                expand = c.args.get('expand', False)
                args2 = remove_keys(c.args, ["label_head", "label_tail", "def_val", "init_val", "help_text",
                                             "style", "size", "color", "side", "anchor", "expand"])

                val = svars.get(var_name, '')
                if tkvar_prev:
                    var = tkvar_prev
                else:
                    var = tkinter.StringVar(value = val)

                mk_label(c, "label_head")

                self.tkvars.set(var_name, var)
                widget = tkinter.Entry(w, textvariable = var, **args2)
#                var.set(val)
                widget.delete(0,"end")
                widget.insert(0, val)                
                if 'width' in args2:
                    widget.pack(side = tkinter.LEFT, anchor = 'w')
                else:
                    widget.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
                mk_label(c, "label_tail")

                if help_text and help_text != '':
                    button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
                    button.pack(side = tkinter.LEFT)

                if show_def_val:
                    button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
                    button.pack(side = tkinter.LEFT)

                if side is None:
                    side = tkinter.TOP
                if anchor is None:
                    anchor = 'w'
                w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)

            elif c.widget == 'text':
                w = tkinter.Frame(frame)
                mk_label(c, "label_head")

                args2 = remove_keys(c.args, ["label_head", "label_tail", "text"])
                widget = tkinter.Text(w, **args2)
                self.vars[c.var] = widget
                widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)
#                widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.BOTH)
                widget.insert(0., c.args.get("text", ""))

                mk_label(c, "label_tail")
                w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
#                w.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)

            elif c.widget == 'checkbox':
                if self.frame:
                    w = self.frame
                else:
                    w = tkinter.Frame(frame)

                side   = c.args.get('side', None)
                anchor = c.args.get('anchor', None)
                args2 = remove_keys(c.args, ["label_head", "label_tail", "def_val", "init_val", "help_text",
                                             "style", "size", "color", "side", "anchor"])

                if init_val is True or pint(init_val) != 0:
                    init_val = True
                else:
                    init_val = False
                var = tkinter.BooleanVar(value = init_val)
                self.tkvars.set(var_name, var)

                mk_label(c, "label_head")

                widget = tkinter.Checkbutton(w, variable = self.tkvars.get(var_name), **args2)
                widget.pack(side = tkinter.LEFT, anchor = 'w')

                if show_def_val:
                    button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
                    button.pack(side = tkinter.LEFT)

                if help_text:
                    button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
                    button.pack(side = tkinter.LEFT)
                
                mk_label(c, "label_tail")

                if side is None:
                    side = tkinter.TOP
                if anchor is None:
                    anchor = 'w'
                w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)

            elif c.widget == 'listbox' or c.widget == 'combobox' or c.widget == 'radiobutton':
                if self.frame:
                    w = self.frame
                else:
                    w = tkinter.Frame(frame)

                side   = c.args.get('side', None)
                anchor = c.args.get('anchor', None)
                expand = c.args.get('expand', False)
                args2 = remove_keys(c.args, 
                            ["label_head", "label_tail", "text", "def_val", "init_val", "values",
                             "style", "size", "color", "side", "anchor", "expand"])
                for i in range(10):
                    args2 = remove_keys(args2, [f"button{i+1}_text", f"button{i+1}_command"])

                init_index = None
# Search index by removing comment
                if init_val is None:
                    init_val = 0
                    init_index = 0
                elif init_val == '':
                    init_index = 0
                else:
                    init_val_nosp = f"{init_val}".replace(' ', '').replace('[', '(').replace(']', ')').lower()
                    for idx in range(len(options)):
                        s = options[idx]
                        s2 = re.sub(r'\s*#.*$', '', s).strip()
#                        print("idx=", var_name, idx, init_val, s, s2)
                        if init_val == s2 or init_val == s:
                            init_val = s
                            init_index = idx
                            break

                        s2_nosp = s2.replace(' ', '').replace('[', '(').replace(']', ')').lower()
                        if init_val_nosp == s2_nosp:
                            init_val = s
                            init_index = idx
                            break

# Case that init_index specifies the index number
                if init_index is None:
                    v = pint(init_val, defval = None, strict = True)
                    if v is not None and 0 <= v < len(options):
                        init_index = v
                        init_val = options[v]

                if init_index is None:
                    init_val = '*** choose ***'

                mk_label(c, "label_head")

                if c.widget == 'listbox':
                    lbvar = tkinter.StringVar(value = options)
                    widget = tkScrolledListbox(w, listvariable = lbvar, **args2)
                    widget.frame.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
                    if init_index is not None:
                        widget.selection_set(init_index)
                        widget.see(init_index)
                    self.tkvars.set(var_name, widget)
                    self.widget_vars.set(var_name, widget)

                    for i in range(10):
                        add_button(w, c.args.get(f"button{i+1}_text", None),
                                   c.args.get(f"button{i+1}_command", None),
                                   tkinter.LEFT, 'nw')

                elif c.widget == 'combobox':
                    def add_combobox(self, w, var_name, options, args2):
#                        cbvar = tkinter.StringVar(value = init_val)
#                        widget = tkinter.ttk.Combobox(w, textvariable = cbvar, **args2)
                        widget = tkinter.ttk.Combobox(w, values = options, **args2)
                        self.tkvars.set(var_name, widget)
#                        var = tkinter.StringVar(value = init_val)
#                        widget = tkinter.ttk.Combobox(w, values = c.vars, textvariable = var, **args2)
                        widget.set(init_val)
                        widget.pack(side = tkinter.LEFT, anchor = 'w', expand = expand, fill = tkinter.X)
                        self.tkvars.set(var_name, widget)
                        self.widget_vars.set(var_name, widget)

#                    args2 = remove_keys(c.args, ["option"])
                    add_combobox(self, w, var_name, options, args2)
#                    widget = self.tkvars.get(var_name)
#                    print("widget=", var_name,widget)
#                    widget.delete(0, tkinter.END)
#                    widget.insert(tkinter.END, s)

                    for i in range(10):
                        add_button(w, c.args.get(f"button{i+1}_text", None),
                                   c.args.get(f"button{i+1}_command", None),
                                   tkinter.LEFT, 'nw')

                elif c.widget == 'radiobutton':
                    var = tkinter.IntVar(value = init_index)
                    self.tkvars.set(var_name, var)
                    for i in range(len(options)):
                        s = options[i]
                        radio = tkinter.Radiobutton(w, text = s, value = i, variable = self.tkvars.get(var_name))
                        radio.pack(side = tkinter.TOP, anchor = 'w')
 
                mk_label(c, "label_tail")

                if side is None:
                    side = tkinter.TOP
                if anchor is None:
                    anchor = 'w'
                w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)

            elif c.widget == 'spinbox':
                if self.frame:
                    w = self.frame
                else:
                    w = tkinter.Frame(frame)

                side   = c.args.get('side', None)
                anchor = c.args.get('anchor', None)
                args2 = remove_keys(c.args, ["label_head", "label_tail", "text", "var_type", 
                                             "help_text", "entry_width", "def_val", "init_val",
                                             "side", "anchor"])

                if tkvar_prev:
                    var = tkvar_prev
                else:
                    if var_type == 'int':
                        var = tkinter.IntVar(value = init_val)
                    elif var_type == 'str':
                        var = tkinter.StringVar(value = init_val)
                    elif var_type == 'double' or var_type == 'float':
                        var = tkinter.DoubleVar(value = init_val)
                    else:
                        print(f"\nError in tktkinter.add_widget(): Invalid var_type [{var_type}]")
                        continue

                    self.tkvars.set(var_name, var)

                mk_label(c, "label_head")

                widget = tkSpinbox(parent = w, app = self.app, 
                        textvariable = self.tkvars.get(var_name), width = entry_width, **args2)
#                widget = tkinter.Entry(w, textvariable = var, **args2)
                widget.pack(side = tkinter.LEFT, anchor = 'w') #, expand = True, fill = tkinter.X)

                self.widget_vars.set(var_name, widget)

                mk_label(c, "label_tail")

                if show_def_val:
                    button = Button(self.app, w, text = 'default', command = lambda: set_default(def_val, self.tkvars.get(var_name)))
                    button.pack(side = tkinter.LEFT)

                if help_text and help_text != '':
                    button = Button(self.app, w, text = '?', command = lambda: show_help(help_text))
                    button.pack(side = tkinter.LEFT)

                if side is None:
                    side = tkinter.TOP
                if anchor is None:
                    anchor = 'w'
                w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)

            elif c.widget == 'choose_file' or c.widget == 'choose_dir':
                show_path_button   = c.args.get("show_path_button", 1)
                show_option_button = c.args.get("show_option_button", 1)
                example_data       = c.args.get("example_data", None)
                entry_type         = c.args.get("entry_type", "combobox")
#                entry_type        = c.args.get("entry_type", "entry")
                entry_filemask     = c.args.get("entry_filemask", "*;*.*")
                entry_height       = c.args.get("entry_height", 5)
                side               = c.args.get("side", None)
                anchor             = c.args.get("anchor", None)
                check_exist        = c.args.get("check_exist", 1)

                args2 = remove_keys(c.args, ["label_head", "label_tail", "text", 
                                    "button_text", "var_type", 
                                    "from", "to", "increment", "entry_width", 
                                    "show_path_button", "show_option_button", "show_data", "example_data", 
                                    "def_val", "init_val", "file_type", "initialdir", 
                                    "entry_type", "entry_filemask",
                                    "side", "anchor", "check_exist"])
                for i in range(10):
                    args2 = remove_keys(args2, [f"button{i+1}_text", f"button{i+1}_command"])
                
                w = tkinter.Frame(frame)
                mk_label(c, "label_head")

                def combobox_sel_changed(e, cb, entry_filemas, entry_type):
                    cur_path = cb.get()
                    if not os.path.isdir(cb.prev_val):
                        dirname, basename, filebody, ext = split_file_path(cb.prev_val)
                    else:
                        dirname = cb.prev_val
                    m = re.match(r'\[(.*)\]$', cur_path)
                    isdir = False
                    if m:
                        cur_path = m.groups()[0]
                        isdir = True

                    new_path = os.path.join(dirname, cur_path)
                    if isdir:
                        dirs, files = get_file_list(new_path, entry_filemask)
                        for f in files:
                            dirs.append(f)
                            
                        if 'combo' in entry_type:
                            cb['values'] = dirs
                        else:
                            pass
                    new_path = os.path.normpath(new_path)
                    cb.set(new_path)
                    cb.prev_val = new_path

                optiosn = ()
                if entry_type == 'entry':
                    if tkvar_prev:
                        entry_var = tkvar_prev
                    else:
                        entry_var = tkinter.StringVar(value = init_val)
                    self.tkvars.set(var_name, entry_var)
                    entry = tkinter.Entry(w, textvariable = self.tkvars.get(var_name), 
                                width = entry_width, **args2)
                else:
                    dirs, files = get_file_list(init_val, entry_filemask)
                    for f in files:
                        dirs.append(f)

                    if 'combo' in entry_type:
                        entry = tkinter.ttk.Combobox(w, values = dirs, width = entry_width, **args2)
                        entry.bind('<<ComboboxSelected>>', lambda e: combobox_sel_changed(e, entry, entry_filemask, entry_type))
                        entry.set(init_val)
                    else:
                        v = tkinter.StringVar(value = dirs)
                        entry = tkinter.Listbox(w, listvariable = v, width = entry_width, height = entry_height)
                        entry = tkScrolledListbox(w, listvariable = v, width = entry_width, **args2)
                        entry.bind('<<ListboxSelected>>', lambda e: combobox_sel_changed(e, entry, entry_filemask, entry_type))

                    self.tkvars.set(var_name, entry)
                    entry.prev_val = init_val
                    entry_var = entry

                entry.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)

                mk_label(c, "label_tail")

                if c.widget == 'choose_file':
                    callback = lambda: path_button_click(self.app, entry_var, 
                                    ini_dir = ini_dir, file_type = file_type, entry_type = entry_type, check_exist = check_exist)
                else:
                    callback = lambda: folder_button_click(self.app, entry_var, 
                                    ini_dir = ini_dir, entry_type = entry_type)

                if show_path_button:
                    button = Button(self.app, w, text = self.app.p(button_text), command = callback)
                    button.pack(side = tkinter.LEFT)

                if show_option_button:
                    config = self.app.config
#                    applications = self.app.get_external_apps([])
                    applications = self.app.get_external_apps([config.get("editor_path", None), config.get("vesta_path", None)])
                    applications = list(dict.fromkeys(applications))
                    var2 = tkinter.StringVar(value = 'app')
                    optionmenu = tkinter.OptionMenu(w, var2, *applications,
                                command = lambda e: execute_command(
                                            self.app, 
                                            working_dir = '.', 
                                            command = var2.get(), 
                                            files = [self.tkvars.get(var_name).get()]
                                            )
                                )
                    optionmenu.configure(width = 3)
                    optionmenu.configure(height = 1)
                    optionmenu.configure(font = ("", 10))
                    optionmenu.pack(side = tkinter.LEFT)

                if example_data is not None:
                    config = self.app.config
                    data_list = example_data.split('#')
                    var3 = tkinter.StringVar(value = 'example')

                    def set_example_data(e, var):
                        val = var.get()
                        if val != 'example':
                            entry_var.set(val)

                    optionmenu = tkinter.OptionMenu(w, var3, *data_list,
                                command = lambda e: set_example_data(e, var3)
                                )
                    optionmenu.configure(width = 7)
                    optionmenu.configure(height = 1)
                    optionmenu.configure(font = ("", 10))
                    optionmenu.pack(side = tkinter.LEFT)
                
                for i in range(10):
                    add_button(w, c.args.get(f"button{i+1}_text", None),
                               c.args.get(f"button{i+1}_command", None),
                               tkinter.LEFT, 'w')

                if side is None:
                    side = tkinter.TOP
                if anchor is None:
                    anchor = 'w'
                w.pack(side = side, anchor = anchor, expand = True, fill = tkinter.X)

            else:
                print("")
                print( "#=========================================================================================")
                print(f"#  Warning in tktkinter.tkCustomDialog_by_config.body(): Invalid widget [{c.widget}]")
                print( "#=========================================================================================")
                print("")

class tkdialog_yesno(tkinter.simpledialog.Dialog):
    def __init__(self, master, title = None, message = None, width = 80):
        self.ret     = None
        self.message = message
        self.width   = width

        super().__init__(parent = master, title = title)

    def body(self, master) -> None:
        frame           = tkinter.Frame(master)
        frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
        widget = tkinter.Label(master, text = self.message, width = self.width)
        widget.pack(side = tkinter.LEFT, anchor = 'w', expand = True, fill = tkinter.X)

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

class tkCustomDialog_by_config(tkinter.simpledialog.Dialog):
    def __init__(self, master, app = None, title = None, config = None, buttons = None, modeless = False, callback = None, is_print = False):
        self.app      = app
        self.ret      = None
        self.config   = config
        if buttons:
            self.buttons = buttons.lower()
        else:
            self.buttons = "okcancel"
        self.modeless = modeless
#        if not modeless:
#            self.grab_set()
        self.callback = callback
        self.is_print = is_print
        self.vars     = {}
        self.tkvars      = tkParams()
        self.widget_vars = tkParams()
#        print("app=", app)
        if app:
#            print("set vars to app")
            app.dialog_vars   = self.vars
            app.dialog_tkvars = self.tkvars
            app.dialog_widget_vars = self.widget_vars
        self.frame    = None

        super().__init__(parent = master, title = title)

    def body(self, master) -> None:
        config = self.config
#        print("config=", config)
        frame              = tkinter.Frame(master)
        frame.pack(side    = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.X)
#        frame.pack(side    = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
        self.root_frame    = frame
        self.current_frame = frame
        for c in config:
            add_widget(self, c)

    def buttonbox(self):
        frame = tkinter.Frame(self)
        if "ok" in self.buttons:
            w = tkinter.Button(frame, text = "OK", width = 10, command = self.ok, default = tkinter.ACTIVE)
            w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
            self.bind("<Return>", self.ok)
        if "cancel" in self.buttons:
            w = tkinter.Button(frame, text = "Cancel", width = 10, command = self.cancel, default = tkinter.ACTIVE)
            w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
            self.bind("<Escape>", self.cancel)
        if "yes" in self.buttons:
            w = tkinter.Button(frame, text = "Yes", width = 10, command = self.ok, default = tkinter.ACTIVE)
            w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
            self.bind("<Return>", self.ok)
        if "no" in self.buttons:
            w = tkinter.Button(frame, text = "No", width = 10, command = self.cancel, default = tkinter.ACTIVE)
            w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
            self.bind("<Escape>", self.cancel)
        if "close" in self.buttons:
            w = tkinter.Button(frame, text = "Close", width = 10, command = self.ok, default = tkinter.ACTIVE)
            w.pack(side = tkinter.LEFT, padx = 5, pady = 5)
            self.bind("<Return>", self.ok)
            self.bind("<Escape>", self.cancel)
        frame.pack()
        
    def update_vars(self):
        config = self.config
        tkvars = self.tkvars
        widget_idx = {}

        config = self.config
        tkvars = self.tkvars
        widget_idx = {}
        wtypes = {}
        for i in range(len(config)):
            c = config[i]
            wtypes[c.var] = c.widget
            widget_idx[c.var] = i

        self.ret = {}
        if self.is_print:
            print("")
            print("Return keys in tkCustomDialog.apply():")
        for key in tkvars.__dict__.keys():
            if re.match(r'_', key):
                continue

            var = tkvars.get(key)
            widget_type = wtypes[key]
#            print("w=", widget_type, var)

            if widget_type == 'checkbox':
                val = var.get()
                if val is True:
                    val = 1
                else:
                    val = 0
            elif widget_type == 'radiobutton':
                idx_button = var.get()
                if idx_button is not None:
                    widx = widget_idx[key]
                    val = config[widx].vars[idx_button]
                else:
                    val = idx_button
            elif widget_type == 'text':
                val = var.get(0., tkinter.END)
            elif widget_type == 'listbox': # or widget_type == 'combobox':
                sel_idx = var.curselection()
                if sel_idx is None:
                    val = ''
                elif type(sel_idx) is int:
                    val = var.get(sel_idx)
                elif type(sel_idx) is tuple:
                    if len(sel_idx) >= 1:
                        val = var.get(sel_idx)
                    else:
                        val = ''
                else:
                    val = ''
                
            else:
                try:
                    val = var.get()
                except:
                    print("")
                    print( "Warning in tktkinter.tkCustomDialog.apply():")
                    print(f"    Can't get value for var_name={key} widget_type={widget_type}")
                    print(f"       var={var}")
                    val = var

            self.ret[key] = val
            if self.is_print:
                print(f"  {key}: {val}")

    def apply(self):  # ダイアログボックスを閉じた後
        self.update_vars()

class tkSelectDialog(tkinter.simpledialog.Dialog):
    def __init__(self, master, app = None, title = None, 
                varname = None, message = "", options = [],
                width = 30, height = 5,
                def_index = None, is_print = False):
        self.app     = app
        self.ret     = None
        self.width   = width
        self.height  = height
        self.message = message
        self.options   = options
        self.def_index = def_index
        self.is_print  = is_print

        super().__init__(parent = master, title = title)

    def body(self, master) -> None:
#        print("def=", self.def_val)

        label = tkinter.Label(master, text = self.message)
        label.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)

        self.lbvar = tkinter.StringVar(value = self.options)
        lb = tkScrolledListbox(master, listvariable = self.lbvar, width = self.width, height = self.height)
#        lb = tkinter.Listbox(master, listvariable = self.lbvar, width = self.width, height = self.height)
        self.listbox = lb
        lb.frame.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)

        if self.def_index is not None:
            lb.selection_set(self.def_index)
            lb.see(self.def_index)

    def apply(self):  # ダイアログボックスを閉じた後
        sel_idx = self.listbox.curselection()
        if sel_idx is None:
            return

        self.ret = self.listbox.get(sel_idx)

        if self.is_print:
            print("tkSelectDialog:")
            print("  selected:", self.ret)

class tkInputDialog(tkinter.simpledialog.Dialog):
    def __init__(self, master, app = None, title = None, message = '',
                width = 30, height = 10, def_val = '', is_print = False) -> None:
        self.app     = app
        self.ret     = None
        self.width   = width
        self.height  = height
        self.message = message
        self.def_val = def_val
        self.is_print = is_print

        super().__init__(parent = master, title = title)

    def body(self, master) -> None:
#        print("def=", self.def_val)

        label = tkinter.Label(master, text = self.message)
        label.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)

#        self.textvariable = tkinter.StringVar(value = self.def_val)
#        text = tkinter.Entry(master, textvariable = self.textvariable, width = self.entry_width)
        text = tkinter.Text(master, height = self.height, width = self.width)
        text.insert(0., self.def_val)
        text.pack(side = tkinter.TOP, anchor = 'w', expand = True, fill = tkinter.BOTH)
        self.text = text

    def apply(self):  # ダイアログボックスを閉じた後
#        text = self.textvariable.get()
        text = self.text.get(0., tkinter.END)
        self.ret = text

        if self.is_print:
            print("Input dialog:")
            print("  input_dialog_text:", text)

