Ejemplo n.º 1
0
    def AutoRegisterClass_handler(self,Class_Tuple,Class_Info):
        Waitwindow = tix.Toplevel()
        Waitwindow.title("자동 신청 - %s"%(Class_Info[3]))
        Waitwindow.resizable(0,0)
        self.AutoRegisterEnabled = True
        Waitwindow.protocol("WM_DELETE_WINDOW",lambda:self.OnAutoWindowClose(Waitwindow))
        Label(Waitwindow,text="강좌이름").grid(row=0,column=0)
        Label(Waitwindow,text=Class_Info[3]).grid(row=0,column=1)
        Label(Waitwindow,text="현재 보인아이 시간").grid(row=1,column=0)
        literal = "%d:%d:%d"%(datetime.datetime.now().hour, datetime.datetime.now().minute, datetime.datetime.now().second)
        clocklabel = Label(Waitwindow, text=literal)
        clocklabel.grid(row=1, column=1)
        clocklabel.after(500, lambda: self.UpdateBoiniClock(clocklabel))
        Label(Waitwindow, text="자동신청 시간").grid(row=2,column=0)
        Label(Waitwindow, text="%s"%(time.strftime("%H:%M:%S",time.localtime(self.AutoRegisterTime.get())))).grid(row=2, column=1)
        Label(Waitwindow, text="상태").grid(row=3, column=0)
        statuslabel = Label(Waitwindow,text="대기중",fg="green")
        statuslabel.grid(row=3,column=1)
        Label(Waitwindow, text="기기등록 확인").grid(row=4,column=0)
        Label(Waitwindow, textvariable=self.checkincrement).grid(row=4,column=1)
        textframe = Frame(Waitwindow)
        textframe.grid(row=5,rowspan=3,column=0,columnspan=2)

        infotext = Text(textframe, borderwidth=1, relief=SUNKEN)
        infotext.pack(side=LEFT,expand=YES,fill=BOTH)
        infotext.insert(END, "자동신청이 이 강좌에 한해 활성화되었습니다. 취소하려면 이 창을 끄면 됩니다.\n")
        infotext.insert(END, str(Class_Tuple)+"\n")
        infotext.config(state=DISABLED)

        textscroll = Scrollbar(textframe,command=infotext.yview)
        textscroll.pack(side=RIGHT,fill=Y)
        infotext.config(yscrollcommand=textscroll.set)
        self.devicecheckcount.set(0)
        root.after(500,lambda: self.CheckAutoRegister(Waitwindow,statuslabel,infotext, Class_Tuple))
Ejemplo n.º 2
0
def text_view(view_text: str,
              title='TkPy text view',
              geometry=None,
              min_size=None,
              max_size=None,
              base_root=None):
    def Copy():
        copy_text = text.get(tk.SEL_FIRST, tk.SEL_LAST) or text.get(
            0.0, tk.END)
        pyperclip.copy(copy_text)

    root = tk.Toplevel(base_root)
    root.transient(base_root)
    root.title(title)
    root.resizable(0, 0)
    if geometry is not None:
        root.geometry(geometry)
    if min_size:
        root.minsize(*min_size)
    if max_size:
        root.maxsize(max_size)
    text = tk.Text(root)
    ycrollbar = tk.Scrollbar(root)
    Frame = tk.Frame(root)
    CopyButton = ttk.Button(Frame, text='复制', command=Copy)
    CloseButton = ttk.Button(Frame, text='关闭', command=root.destroy)
    text.insert(tk.END, view_text)
    CopyButton.pack(fill=tk.X, expand=True, side=tk.RIGHT)
    CloseButton.pack(fill=tk.X, expand=True, side=tk.LEFT)
    ycrollbar.config(command=text.yview)
    Frame.pack(side=tk.BOTTOM, fill=tk.X, expand=True)
    ycrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    text.pack(fill=tk.BOTH, expand=True)
    text.config(yscrollcommand=ycrollbar.set, state=tk.DISABLED)
    root.mainloop()
Ejemplo n.º 3
0
 def about_license_clicked(self):
     about = tix.Toplevel()  # bg="#0f0" does not work
     about.title("License")
     about.resizable(width=False, height=False)
     set_window_icon(about)
     about.bind('<Escape>', lambda e: about.destroy())
     ###
     msg1 = tix.Message(
         about,
         width=420,
         text=licenseText,
         font=("DejaVu Sans", 9, "bold"),
     )
     msg1.pack(fill="x", expand=True)
     ###########
     frame = tix.Frame(about)
     closeB = ttk.Button(
         frame,
         text="Close",
         command=about.destroy,
         # bg="#ff0000",
         # activebackground="#ff5050",
     )
     closeB.pack(side="right")
     frame.pack(fill="x")
Ejemplo n.º 4
0
    def valueMenuItemCustomSelected(self, treev, format, optName, menu=None):
        if menu:
            menu.destroy()
            self.menu = None

        value = treev.set(optName, self.valueCol)

        dialog = tix.Toplevel(master=treev)  # bg="#0f0" does not work
        dialog.resizable(width=True, height=True)
        dialog.title(optName)
        set_window_icon(dialog)
        dialog.bind('<Escape>', lambda e: dialog.destroy())

        px, py, pw, ph = decodeGeometry(treev.winfo_toplevel().geometry())
        w = 300
        h = 100
        dialog.geometry(
            encodeGeometry(
                px + pw // 2 - w // 2,
                py + ph // 2 - h // 2,
                w,
                h,
            ))

        frame = tix.Frame(master=dialog)

        label = ttk.Label(master=frame, text="Value for " + optName)
        label.pack()

        entry = ttk.Entry(master=frame)
        entry.insert(0, value)
        entry.pack(fill="x")

        def okClicked(event=None):
            value = entry.get()
            treev.set(optName, self.valueCol, value)
            treev.set(optName, "#1", "1")  # enable it
            col_w = tkFont.Font().measure(value)
            if treev.column("Value", width=None) < col_w:
                treev.column("Value", width=col_w)
            dialog.destroy()

        entry.bind("<Return>", okClicked)

        label = ttk.Label(master=frame)
        label.pack(fill="x")

        button = ttk.Button(
            frame,
            text="Ok",
            command=okClicked,
            # bg="#ff0000",
            # activebackground="#ff5050",
        )
        button.pack(side="right")
        ###
        frame.pack(fill="x")
        dialog.focus()
Ejemplo n.º 5
0
    def ShowClock(self):
        self.SetServerTimeOffset()
        clockwindow = tix.Toplevel()

        clockwindow.resizable(0,0)
        tix.Label(clockwindow, text="현재 보인아이 시간").pack()
        clocklabel = Label(clockwindow, text="00:00:00", font=("TkDefaultFont", 50, "bold"))
        clocklabel.pack(expand=YES, fill=BOTH)
        clockwindow.update_idletasks()
        fontscale = Scale(clockwindow,label="글자크기",from_=10, to=200, orient=HORIZONTAL, resolution=10)
        fontscale.pack(fill=Y,anchor=S)
        fontscale.set(50)
        fontscale.bind("<ButtonRelease-1>",lambda x: self.UpdateStandaloneClock(clocklabel, fontscale))
        clocklabel.after(100, self.UpdateBoiniClock(clocklabel))
Ejemplo n.º 6
0
 def showsets(self):
         if self.set_codes is None :
                 self.set_codes = list_sets()
         tl = tix.Toplevel(self.main.master)
         lb = tix.ScrolledListBox(tl)
         lb.listbox.insert(0, *[x[0] for x in self.set_codes])
         lb.listbox.configure(width=30, height=30, bg="#ffffff")
         tl.bind("<Escape>", tl.destroy)
         def finish(event):
                 index = lb.listbox.curselection()
                 if not len(index) :
                         return
                 self.search(self.set_codes[index[0]][1])
                 tl.destroy()
         lb.listbox.bind("<<ListboxSelect>>", finish)
         lb.pack(fill="both",expand=True)
Ejemplo n.º 7
0
    def AddPersonalInfo(self):

        Register_window = tix.Toplevel(self.master)
        Register_window.grab_set()
        Register_window.resizable(0,0)

        Label(Register_window,text="이름").grid(row=0,column=0)
        Label(Register_window,text="학번").grid(row=1,column=0)
        Label(Register_window,text="메모").grid(row=2,column=0)
        Entry(Register_window,textvariable=self.Student_Name).grid(row=0,column=1)
        Entry(Register_window,textvariable=self.Student_ID).grid(row=1,column=1)
        Entry(Register_window,textvariable=self.Memo).grid(row=2,column=1)
        Register_window.columnconfigure(0,weight=1)
        Register_window.columnconfigure(0,weight=1)
        Label(Register_window,text="이름과 학번은 어떻게 치든 본인걸로 등록됩니다.",foreground="red").grid(row=3,column=0,columnspan=2)
        Button(Register_window,text="닫기",command=lambda:Register_window.destroy()).grid(row=4,column=0,columnspan=2)
Ejemplo n.º 8
0
 def __init__(self, base_root=None):
     self.root = tk.Toplevel(base_root)
     self.root.transient(base_root)
     self.root.minsize(400, 50)
     self.root.resizable(0, 0)
     self.all_file_types = [('图片文件', ('.png', '.jpg', '.jpeg'))]
     self.root.title('文字识别')
     self.Frame = tk.Frame(self.root)
     self.scrollbar = tk.Scrollbar(self.root, orient=tk.HORIZONTAL)
     self.LabelFrame = ttk.LabelFrame(self.root, text='文件目录: ')
     self.Entry = ttk.Entry(self.LabelFrame,
                            xscrollcommand=self.scrollbar.set)
     self.scrollbar.config(command=self.Entry.xview)
     self.Entry.config(state=tk.DISABLED)
     self.Button = ttk.Button(self.Frame, text='选择文件', command=self.askfile)
     self.submmitButton = ttk.Button(
         self.Frame, text='识别', command=lambda: self.ok(self.Entry.get()))
Ejemplo n.º 9
0
 def OnAutomate(self):
     Register_window = tix.Toplevel(self.master)
     Register_window.grab_set()
     Register_window.resizable(0, 0)
     clocklabel =  Label(Register_window,text="현재 이 컴퓨터의 시간: %s:%s:%s"%(str(datetime.datetime.now().hour).rjust(2,"0"), str(datetime.datetime.now().minute).rjust(2,"0"),str(datetime.datetime.now().second).rjust(2,"0")))
     boini_clock_label = Label(Register_window,text="현재 보인아이 홈페이지의 시간: (동기화 중...잠시만 기다려주세요)")
     clocklabel.grid(row=0,column=0,columnspan=2)
     boini_clock_label.grid(row=1, column=0, columnspan=2)
     clocklabel.after(500, lambda: self.UpdateOnAutomateClock(clocklabel, True))
     boini_clock_label.after(1000, lambda: self.GetServerTime(boini_clock_label))
     Label(Register_window,text="등록시작을 원하는 시간을 현재 보인아이의 시간을 기준으로 24시간:분:초 형식으로 입력해주세요\n(예:8시부터 신청을 원하면 20:00:00). 이 사간부터 프로그램은 될때까지 등록을 계속 합니다.").grid(row=2,column=0,columnspan=2)
     Label(Register_window, text="시간(24시간 형식):분:초").grid(row=3, column=0)
     timeentry = Entry(Register_window,textvariable=self.AutoRegisterInput)
     timeentry.grid(row=3, column=1)
     timeentry.bind("<KeyRelease>",self.OnAutomateCallBack)
     #Button(Register_window, text="설정하기", command= lambda: self.OnAutomateCallBack("event")).grid(row=3, column=0, columnspan=2)
     Label(Register_window, textvariable=self.AutoRegisterInfo).grid(row=4,column=0,columnspan=2)
     Button(Register_window, text="닫기", command=lambda: Register_window.destroy()).grid(row=5, column=0, columnspan=2)
Ejemplo n.º 10
0
   def __enter__( self ):
      # Remove the app window from the display
      self._root.withdraw( )
      
      # Calculate the geometry to center the splash image
      scrnWt = self._root.winfo_screenwidth( )
      scrnHt = self._root.winfo_screenheight( )
      
      imgWt = self._image.width()
      imgHt = self._image.height()
      
      imgXPos = (scrnWt / 2) - (imgWt / 2)
      imgYPos = (scrnHt / 2) - (imgHt / 2)

      # Create the splash screen      
      self._splash = Tix.Toplevel()
      self._splash.overrideredirect(1)
      self._splash.geometry( '+%d+%d' % (imgXPos, imgYPos) )
      Tix.Label( self._splash, image=self._image, cursor='watch' ).pack( )

      # Force Tk to draw the splash screen outside of mainloop()
      self._splash.update( )
Ejemplo n.º 11
0
 def about_credits_clicked(self):
     about = tix.Toplevel()  # bg="#0f0" does not work
     about.title("Credits")
     about.resizable(False, False)
     set_window_icon(about)
     ###
     msg1 = tix.Message(
         about,
         width=500,
         text="\n".join(authors),
         font=("DejaVu Sans", 9, "bold"),
     )
     msg1.pack(fill="x", expand=True)
     ###########
     frame = tix.Frame(about)
     closeB = tix.Button(
         frame,
         text="Close",
         command=about.destroy,
         # bg="#ff0000",
         # activebackground="#ff5050",
     )
     closeB.pack(side="right")
     frame.pack(fill="x")
Ejemplo n.º 12
0
 def about_license_clicked(self):
     about = tix.Toplevel()  ## bg='#0f0' does not work
     about.title('License')
     about.resizable(False, False)
     about.wm_iconbitmap('@%s' % xbmLogo)
     ###
     msg1 = tix.Message(
         about,
         width=420,
         text=licenseText,
         font=('DejaVu Sans', 9, 'bold'),
     )
     msg1.pack(fill='x', expand=True)
     ###########
     frame = tix.Frame(about)
     closeB = tix.Button(
         frame,
         text='Close',
         command=about.destroy,
         #bg='#ff0000',
         #activebackground='#ff5050',
     )
     closeB.pack(side='right')
     frame.pack(fill='x')
Ejemplo n.º 13
0
 def about_credits_clicked(self):
     about = tix.Toplevel()  ## bg='#0f0' does not work
     about.title('Credits')
     about.resizable(False, False)
     set_window_icon(about)
     ###
     msg1 = tix.Message(
         about,
         width=500,
         text='\n'.join(authors),
         font=('DejaVu Sans', 9, 'bold'),
     )
     msg1.pack(fill='x', expand=True)
     ###########
     frame = tix.Frame(about)
     closeB = tix.Button(
         frame,
         text='Close',
         command=about.destroy,
         #bg='#ff0000',
         #activebackground='#ff5050',
     )
     closeB.pack(side='right')
     frame.pack(fill='x')
Ejemplo n.º 14
0
def display_window(screen_def, query=None):
    """
      Creates a window to display the HList if it doesn't already exist 
     and populates the HList with the results of the HList's query.
     Returns the query that it ran.
   """
    if debug:
        # Check to see if the ConnectionInfo attributes are set properly
        logger.debug('The connection object = {}'. \
                     format(ConnectionInfo.cx_Oracle_connection_object))
        logger.debug('The connect string = {}'. \
                     format(ConnectionInfo.Oracle_connect_string))
        logger.debug('The current DB = {}'.format(ConnectionInfo.current_db))

    db_connection = ConnectionInfo.cx_Oracle_connection_object
    # Build the HList if it doesn't already exist
    try:
        screen_def['window'].deiconify()
    except:
        if debug:
            logger.debug('Building display for {}'.format(screen_def['name']))
        screen_def['window'] = tix.Toplevel(width=80)
        screen_def['window'].configure
        build_screen(screen_def)

    # Add the database to the HList's title
    title = '{} on {}'.format(screen_def['title'],
                              ConnectionInfo.Oracle_version)
    screen_def['window'].title(title)

    # Clear the current contents of the HList
    screen_def['hlist'].delete_all()
    if query == None:
        # Get the fields that the SQL query is ordered by from the
        # screen's dictionary definition
        query = screen_def['query'].replace('var_order_by',
                                            screen_def['order_by'])

    # Set the column width and get the number of columns to display
    screen_def['hlist'].column_width(0, chars=30)
    total_cols = len(screen_def['columns'])

    # Create a cursor and execute the HList's query
    try:
        cur = db_connection.cursor()
        cur.execute(query)
        # Step through the rows returned by the query and populate the HList
        i = 0
        for row in cur:
            i = i + 1
            screen_def['hlist'].add(i, itemtype=tix.TEXT, text=row[0])
            # Step through the columns of the row and insert the data
            for col_num in range(total_cols):
                screen_def['hlist'].item_create(i,
                                                col_num,
                                                itemtype=tix.TEXT,
                                                text=row[col_num])

        # Set the row count label to the number of rows returned by the query.
        screen_def['varRows'].set('Total Rows: {}'.format(cur.rowcount))

    except Exception as e:
        logger.exception(str(e))
        display_error('display_window Error', str(e))
        screen_def['window'].destroy()

    cur.close()
    # Return the SQL statement that was executed
    screens.displayed_query = query
Ejemplo n.º 15
0
        'title': 'N',
        'summary': '''\
summary of N
very very very long long long long summary
0
1
2
3
4
5
6
:
a''',
        'content': 32
    }]

    top = tix.Toplevel()
    d = matdb_frame.MatDBFrame(top, entries)
    d.grid(row=0, column=0, sticky=tix.N + tix.E + tix.S + tix.W)
    top.rowconfigure(0, weight=1)
    top.columnconfigure(0, weight=1)

    def c():
        print(d.get_current_selection())

    tix.Button(app, text='get value', command=c).pack()

    top.wait_window()

    app.mainloop()
Ejemplo n.º 16
0
 def about_clicked(self):
     about = tix.Toplevel(width=600)  # bg="#0f0" does not work
     about.title("About PyGlossary")
     about.resizable(False, False)
     set_window_icon(about)
     ###
     msg1 = tix.Message(
         about,
         width=350,
         text="PyGlossary %s (Tkinter)" % VERSION,
         font=("DejaVu Sans", 13, "bold"),
     )
     msg1.pack(fill="x", expand=True)
     ###
     msg2 = tix.Message(
         about,
         width=350,
         text=aboutText,
         font=("DejaVu Sans", 9, "bold"),
         justify=tix.CENTER,
     )
     msg2.pack(fill="x", expand=True)
     ###
     msg3 = tix.Message(
         about,
         width=350,
         text=homePage,
         font=("DejaVu Sans", 8, "bold"),
         fg="#3333ff",
     )
     msg3.pack(fill="x", expand=True)
     ###
     msg4 = tix.Message(
         about,
         width=350,
         text="Install PyGTK to have a better interface!",
         font=("DejaVu Sans", 8, "bold"),
         fg="#00aa00",
     )
     msg4.pack(fill="x", expand=True)
     ###########
     frame = tix.Frame(about)
     ###
     button = tix.Button(
         frame,
         text="Close",
         command=about.destroy,
         # bg="#ff0000",
         # activebackground="#ff5050",
     )
     button.pack(side="right")
     ###
     button = tix.Button(
         frame,
         text="License",
         command=self.about_license_clicked,
         # bg="#00e000",
         # activebackground="#22f022",
     )
     button.pack(side="right")
     ###
     button = tix.Button(
         frame,
         text="Credits",
         command=self.about_credits_clicked,
         # bg="#0000ff",
         # activebackground="#5050ff",
     )
     button.pack(side="right")
     ###
     frame.pack(fill="x")
Ejemplo n.º 17
0
    def valueMenuItemCustomSelected(self, treev, format, optName, menu=None):
        if menu:
            menu.destroy()
            self.menu = None

        value = treev.set(optName, self.valueCol)

        dialog = tix.Toplevel(master=treev)  # bg="#0f0" does not work
        dialog.resizable(width=True, height=True)
        dialog.title(optName)
        set_window_icon(dialog)
        dialog.bind('<Escape>', lambda e: dialog.destroy())

        px, py, pw, ph = decodeGeometry(treev.winfo_toplevel().geometry())
        w = 300
        h = 100
        dialog.geometry(
            encodeGeometry(
                px + pw // 2 - w // 2,
                py + ph // 2 - h // 2,
                w,
                h,
            ))

        frame = tix.Frame(master=dialog)

        label = ttk.Label(master=frame, text="Value for " + optName)
        label.pack()

        entry = ttk.Entry(master=frame)
        entry.insert(0, value)
        entry.pack(fill="x")

        prop = Glossary.plugins[format].optionsProp[optName]

        def customOkClicked(event=None):
            rawValue = entry.get()
            if not prop.validateRaw(rawValue):
                log.error(
                    f"invalid {prop.typ} value: {optName} = {rawValue!r}")
                return
            treev.set(optName, self.valueCol, rawValue)
            treev.set(optName, "#1", "1")  # enable it
            col_w = tkFont.Font().measure(rawValue)
            if treev.column("Value", width=None) < col_w:
                treev.column("Value", width=col_w)
            dialog.destroy()

        entry.bind("<Return>", customOkClicked)

        label = ttk.Label(master=frame)
        label.pack(fill="x")

        customOkbutton = newTTKButton(
            frame,
            text="OK",
            command=customOkClicked,
            # bg="#ff0000",
            # activebackground="#ff5050",
        )
        customOkbutton.pack(side="right")
        ###
        frame.pack(fill="x")
        dialog.focus()
Ejemplo n.º 18
0
 def about_clicked(self):
     about = tix.Toplevel(width=600)  ## bg='#0f0' does not work
     about.title('About PyGlossary')
     about.resizable(False, False)
     about.wm_iconbitmap('@%s' % xbmLogo)
     ###
     msg1 = tix.Message(
         about,
         width=350,
         text='PyGlossary %s (Tkinter)' % VERSION,
         font=('DejaVu Sans', 13, 'bold'),
     )
     msg1.pack(fill='x', expand=True)
     ###
     msg2 = tix.Message(
         about,
         width=350,
         text=aboutText,
         font=('DejaVu Sans', 9, 'bold'),
         justify=tix.CENTER,
     )
     msg2.pack(fill='x', expand=True)
     ###
     msg3 = tix.Message(
         about,
         width=350,
         text=homePage,
         font=('DejaVu Sans', 8, 'bold'),
         fg='#3333ff',
     )
     msg3.pack(fill='x', expand=True)
     ###
     msg4 = tix.Message(
         about,
         width=350,
         text='Install PyGTK to have a better interface!',
         font=('DejaVu Sans', 8, 'bold'),
         fg='#00aa00',
     )
     msg4.pack(fill='x', expand=True)
     ###########
     frame = tix.Frame(about)
     ###
     button = tix.Button(
         frame,
         text='Close',
         command=about.destroy,
         #bg='#ff0000',
         #activebackground='#ff5050',
     )
     button.pack(side='right')
     ###
     button = tix.Button(
         frame,
         text='License',
         command=self.about_license_clicked,
         #bg='#00e000',
         #activebackground='#22f022',
     )
     button.pack(side='right')
     ###
     button = tix.Button(
         frame,
         text='Credits',
         command=self.about_credits_clicked,
         #bg='#0000ff',
         #activebackground='#5050ff',
     )
     button.pack(side='right')
     ###
     frame.pack(fill='x')
Ejemplo n.º 19
0
    def buttonClicked(self):
        formatD = self.formatVar.get()
        if not formatD:
            return
        format = Glossary.descFormat[formatD]
        options = self.kindFormatsOptions[self.kind][format]
        optionsProp = Glossary.formatsOptionsProp[format]

        dialog = tix.Toplevel()  # bg="#0f0" does not work
        dialog.resizable(width=True, height=True)
        dialog.title(self.kind + " Options")
        set_window_icon(dialog)
        dialog.bind('<Escape>', lambda e: dialog.destroy())
        ###
        self.valueCol = "#3"
        cols = [
            "Enable",  # bool
            "Name",  # str
            "Value",  # str
            "Comment",  # str
        ]
        treev = ttk.Treeview(
            master=dialog,
            columns=cols,
            show="headings",
        )
        for col in cols:
            treev.heading(
                col,
                text=col,
                #command=lambda c=col: sortby(treev, c, 0),
            )
            # adjust the column's width to the header string
            treev.column(
                col,
                width=tkFont.Font().measure(col.title()),
            )
        ###
        def valueMenuItemSelected(optName, menu, value):
            treev.set(optName, self.valueCol, value)
            treev.set(optName, "#1", "1")  # enable it
            col_w = tkFont.Font().measure(value)
            if treev.column("Value", width=None) < col_w:
                treev.column("Value", width=col_w)
            menu.destroy()
            self.menu = None

        def valueCellClicked(event, optName):
            if not optName:
                return
            prop = optionsProp[optName]
            propValues = prop.values
            if not propValues:
                if prop.customValue:
                    self.valueMenuItemCustomSelected(treev, format, optName,
                                                     None)
                else:
                    log.error(f"invalid option {optName}, values={propValues}"
                              f", customValue={prop.customValue}")
                return
            if prop.typ == "bool":
                rawValue = treev.set(optName, self.valueCol)
                if rawValue == "":
                    value = False
                else:
                    value, isValid = prop.evaluate(rawValue)
                    if not isValid:
                        log.error(f"invalid {optName} = {rawValue!r}")
                        value = False
                treev.set(optName, self.valueCol, str(not value))
                treev.set(optName, "#1", "1")  # enable it
                return
            menu = tk.Menu(
                master=treev,
                title=optName,
                tearoff=False,
            )
            self.menu = menu  # to destroy it later
            if prop.customValue:
                menu.add_command(
                    label="[Custom Value]",
                    command=lambda: self.valueMenuItemCustomSelected(
                        treev, format, optName, menu),
                )
            groupedValues = None
            if len(propValues) > 10:
                groupedValues = prop.groupValues()
            maxItemW = 0
            if groupedValues:
                for groupName, subValues in groupedValues.items():
                    if subValues is None:
                        menu.add_command(
                            label=str(value),
                            command=lambda value=value: valueMenuItemSelected(
                                optName, menu, value),
                        )
                        maxItemW = max(maxItemW,
                                       tkFont.Font().measure(str(value)))
                    else:
                        subMenu = tk.Menu(tearoff=False)
                        for subValue in subValues:
                            subMenu.add_command(
                                label=str(subValue),
                                command=lambda value=subValue:
                                valueMenuItemSelected(optName, menu, value),
                            )
                        menu.add_cascade(label=groupName, menu=subMenu)
                        maxItemW = max(maxItemW,
                                       tkFont.Font().measure(groupName))
            else:
                for value in propValues:
                    value = str(value)
                    menu.add_command(
                        label=value,
                        command=lambda value=value: valueMenuItemSelected(
                            optName, menu, value),
                    )

            def close():
                menu.destroy()
                self.menu = None

            menu.add_command(
                label="[Close]",
                command=close,
            )
            try:
                menu.tk_popup(
                    event.x_root,
                    event.y_root,
                )
                # do not pass the third argument (entry), so that the menu
                # apears where the pointer is on its top-left corner
            finally:
                # make sure to release the grab (Tk 8.0a1 only)
                menu.grab_release()

        def treeClicked(event):
            if self.menu:
                self.menu.destroy()
                self.menu = None
                return
            optName = treev.identify_row(event.y)  # optName is rowId
            if not optName:
                return
            col = treev.identify_column(event.x)  # "#1" to self.valueCol
            if col == "#1":
                value = treev.set(optName, col)
                treev.set(optName, col, 1 - int(value))
                return
            if col == self.valueCol:
                valueCellClicked(event, optName)

        treev.bind(
            "<Button-1>",
            # "<<TreeviewSelect>>", # event.x and event.y are zero
            treeClicked,
        )
        treev.pack(fill="x", expand=True)
        ###
        for optName in options:
            prop = optionsProp[optName]
            comment = prop.typ
            if prop.comment:
                comment += ", " + prop.comment
            row = [
                int(optName in self.values),
                optName,
                str(self.values.get(optName, "")),
                comment,
            ]
            treev.insert("", "end", values=row,
                         iid=optName)  # iid should be rowId
            # adjust column's width if necessary to fit each value
            for col_i, value in enumerate(row):
                value = str(value)
                if col_i == 3:
                    value = value.zfill(
                        20
                    )  # to reserve window width, because it's hard to resize it later
                col_w = tkFont.Font().measure(value)
                if treev.column(cols[col_i], width=None) < col_w:
                    treev.column(cols[col_i], width=col_w)
        ###########
        frame = tix.Frame(dialog)

        ###
        def okClicked():
            for optName in options:
                enable = bool(int(treev.set(optName, "#1")))
                if not enable:
                    if optName in self.values:
                        del self.values[optName]
                    continue
                rawValue = treev.set(optName, self.valueCol)
                prop = optionsProp[optName]
                value, isValid = prop.evaluate(rawValue)
                if not isValid:
                    log.error(f"invalid option value {optName} = {rawValue}")
                    continue
                self.values[optName] = value
            dialog.destroy()

        button = ttk.Button(
            frame,
            text="OK",
            command=okClicked,
            # bg="#ff0000",
            # activebackground="#ff5050",
        )
        button.pack(side="right")
        ###
        frame.pack(fill="x")
        ###
        # x, y, w, h = decodeGeometry(dialog.geometry())
        w, h = 380, 250
        # w and h are rough estimated width and height of `dialog`
        px, py, pw, ph = decodeGeometry(self.winfo_toplevel().geometry())
        # move dialog without changing the size
        dialog.geometry(
            encodeLocation(
                px + pw // 2 - w // 2,
                py + ph // 2 - h // 2,
            ))
        dialog.focus()