├── CTkPopupKeyboard
├── __init__.py
├── numpad.py
└── keyboard.py
├── LICENSE
└── README.md
/CTkPopupKeyboard/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | CustomTkinter PopupKeyboard
3 | Author: Akash Bora
4 | License: MIT
5 | This is an on-screen keyboard widget made for customtkinter.
6 | Homepage: https://github.com/Akascape/CTkPopupKeyboard
7 | """
8 |
9 | __version__ = '0.2'
10 |
11 | from .keyboard import PopupKeyboard
12 | from .numpad import PopupNumpad
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Akash Bora
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CTkPopupKeyboard
2 | **On-Screen Keyboard/Numpad widget for customtkinter entries and textbox.**
3 |
4 | ## Features
5 | - Customize keyboard and numpad styles
6 | - All keyboard characters available
7 | - transparency effect
8 | - double click popup window
9 |
10 | 
11 |
12 | ## Installation
13 | ### [
](https://github.com/Akascape/CTkPopupKeyboard/archive/refs/heads/main.zip)
14 |
15 | **Download the source code, paste the `CTkPopupKeyboard` folder in the directory where your program is present.**
16 |
17 | ## Example Program:
18 | ```python
19 | from CTkPopupKeyboard import PopupKeyboard, PopupNumpad
20 | import customtkinter
21 |
22 | def show_popup():
23 | # Disable/Enable popup
24 | if switch.get()==1:
25 | keyboard.disable = False
26 | numpad.disable = False
27 | else:
28 | keyboard.disable = True
29 | numpad.disable = True
30 |
31 | root = customtkinter.CTk()
32 |
33 | text_box = customtkinter.CTkTextbox(root)
34 | text_box.pack(fill="both", padx=10, pady=10)
35 |
36 | # attach popup keyboard to text_box
37 | keyboard = PopupKeyboard(text_box)
38 |
39 | entry = customtkinter.CTkEntry(root, placeholder_text="Write Something...")
40 | entry.pack(fill="both", padx=10, pady=10)
41 |
42 | # attach popup keyboard to entry
43 | numpad = PopupNumpad(entry)
44 |
45 | switch = customtkinter.CTkSwitch(root, text="On-Screen Keyboard", command=show_popup)
46 | switch.pack(pady=10)
47 | switch.toggle()
48 |
49 | root.mainloop()
50 | ```
51 |
52 | ## Arguments for PopupKeyboard/PopupNumpad
53 | | Parameter | Description |
54 | |-----------| ------------|
55 | | **attach** | parent widget to which the keyboard will be attached |
56 | | x | **optional**, change the horizontal offset of the widget manually |
57 | | y | **optional**, change the vertical offset of the widget manually |
58 | | keywidth | change the default width of the keys |
59 | | keyheight | change the default height of the keys |
60 | | **key_color** | change the fg_color of the buttons |
61 | | text_color | change the text_color of the buttons |
62 | | hover_color | change the hover_color of the buttons |
63 | | **alpha** | change the transparency of the whole keyboard widget (range: 0-1) |
64 | | **fg_color** | change the background frame color of the widget |
65 | | corner | adjust roundness of the frame corners |
66 | | relief | change the button style for keyboard, **only for PopupKeyboard:** (flat, raised, sunken, groove, ridge) |
67 | | point | define if you want the point in numpad, **only for PopupNumpad:** boolean |
68 | | _*Other Button Parameters_ | _All other parameters for ctkbutton/tkbutton can be passed in popupkeyboard_ |
69 |
--------------------------------------------------------------------------------
/CTkPopupKeyboard/numpad.py:
--------------------------------------------------------------------------------
1 | '''
2 | On-screen Popup Numpad for customtkinter
3 | Author: Akash Bora
4 | '''
5 |
6 | from tkinter import *
7 | from customtkinter import *
8 | import sys
9 |
10 | class PopupNumpad(CTkToplevel):
11 |
12 | def __init__(self, attach, x=None, y=None, keycolor=None,
13 | fg_color=None, keyheight: int = 50, keywidth: int = 50,
14 | alpha: float = 0.85, corner=20, point=True, **kwargs):
15 |
16 | super().__init__(takefocus=1)
17 |
18 | self.focus()
19 | self.corner = corner
20 | self.disable = True
21 |
22 | if sys.platform.startswith("win"):
23 | self.overrideredirect(True)
24 | self.transparent_color = self._apply_appearance_mode(self._fg_color)
25 | self.attributes("-transparentcolor", self.transparent_color)
26 | elif sys.platform.startswith("darwin"):
27 | self.overrideredirect(True)
28 | self.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
29 | self.transparent_color = 'systemTransparent'
30 | self.attributes("-transparent", True)
31 | else:
32 | self.attributes("-type", "splash")
33 | self.transparent_color = '#000001'
34 | self.corner = 0
35 | self.withdraw()
36 |
37 | self.disable = False
38 | self.fg_color = ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
39 | self.frame = CTkFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color, corner_radius=self.corner, border_width=2)
40 | self.frame.pack(expand=True, fill="both")
41 | self.attach = attach
42 | self.keycolor = ThemeManager.theme["CTkFrame"]["fg_color"] if keycolor is None else keycolor
43 | self.keywidth = keywidth
44 | self.keyheight = keyheight
45 | self.point = point
46 |
47 | self.resizable(width=False, height=False)
48 | self.transient(self.master)
49 |
50 | self.frame_color = ThemeManager.theme["CTkFrame"]["fg_color"]
51 | self.row1 = CTkFrame(self.frame, fg_color=self.frame_color)
52 | self.row2 = CTkFrame(self.frame, fg_color=self.frame_color)
53 | self.row3 = CTkFrame(self.frame, fg_color=self.frame_color)
54 | self.row4 = CTkFrame(self.frame, fg_color=self.frame_color)
55 |
56 | self.row1.grid(row=1, column=0, pady=(10,0))
57 | self.row2.grid(row=2, column=0, padx=10)
58 | self.row3.grid(row=3, column=0, padx=10)
59 | self.row4.grid(row=4, column=0, pady=(0,10))
60 |
61 | self._init_keys(**kwargs)
62 |
63 | # hide/show PopupNumpad
64 | self.attach.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
65 | self.attach.bind('', lambda e: self._iconify(), add="+")
66 | self.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
67 |
68 | self.update_idletasks()
69 | self.x = x
70 | self.y = y
71 | self._iconify()
72 | self.attributes('-alpha', alpha)
73 |
74 | def _init_keys(self, **kwargs):
75 | self.keys = {
76 | 'row1' : ['7','8','9'],
77 | 'row2' : ['4','5','6'],
78 | 'row3' : ['1','2','3'],
79 | 'row4' : ['.','0','◀']
80 | }
81 |
82 | for row in self.keys.keys():
83 | if row == 'row1':
84 | i = 1
85 | for k in self.keys[row]:
86 | CTkButton(self.row1,
87 | text=k,
88 | width=self.keywidth,
89 | height=self.keyheight,
90 | fg_color=self.keycolor,
91 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
92 | i += 1
93 | elif row == 'row2':
94 | i = 2
95 | for k in self.keys[row]:
96 | CTkButton(self.row2,
97 | text=k,
98 | width=self.keywidth,
99 | height=self.keyheight,
100 | fg_color=self.keycolor,
101 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
102 | i += 1
103 | i = 2
104 | elif row == 'row3':
105 | i = 2
106 | for k in self.keys[row]:
107 | CTkButton(self.row3,
108 | text=k,
109 | width=self.keywidth,
110 | height=self.keyheight,
111 | fg_color=self.keycolor,
112 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
113 | i += 1
114 |
115 | elif row == 'row4':
116 | i = 2
117 | for k in self.keys[row]:
118 | CTkButton(self.row4,
119 | text=k,
120 | width=self.keywidth,
121 | height=self.keyheight,
122 | fg_color=self.keycolor,
123 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
124 | i += 1
125 |
126 | self.up = False
127 | self.hide = False
128 |
129 | def destroy_popup(self):
130 | self.destroy()
131 | self.disable = True
132 |
133 | def _iconify(self):
134 | if self.disable: return
135 | if self.hide:
136 | self.deiconify()
137 | self.focus()
138 | self.hide = False
139 | self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x
140 | self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y
141 | self.geometry('{}x{}+{}+{}'.format(self.frame.winfo_reqwidth(),
142 | self.frame.winfo_reqheight(),
143 | self.x_pos,self.y_pos))
144 | else:
145 | self.withdraw()
146 | self.hide = True
147 |
148 | def _attach_key_press(self, k):
149 | if k == '◀':
150 | try:
151 | text = self.attach.get(0.0, END)
152 | self.attach.delete(0.0, END)
153 | self.attach.insert(0.0, text[:-2])
154 | except TypeError:
155 | text = self.attach.get()
156 | self.attach.delete(0, END)
157 | self.attach.insert(0, text[:-1])
158 | return
159 | if k == ".":
160 | if self.point:
161 | self.attach.insert(INSERT, k)
162 | else:
163 | self.attach.insert(INSERT, k)
164 |
165 |
--------------------------------------------------------------------------------
/CTkPopupKeyboard/keyboard.py:
--------------------------------------------------------------------------------
1 | '''
2 | On-screen Popup Keyboard for customtkinter
3 | Author: Akash Bora
4 | '''
5 |
6 | from tkinter import *
7 | from customtkinter import *
8 | import sys
9 |
10 | class PopupKeyboard(CTkToplevel):
11 |
12 | def __init__(self, attach, x=None, y=None, key_color=None,
13 | text_color=None, hover_color=None, fg_color=None,
14 | keywidth: int = 5, keyheight: int = 2, command=None,
15 | alpha: float = 0.85, corner=20, **kwargs):
16 |
17 | super().__init__(takefocus=0)
18 |
19 | self.focus()
20 | self.corner = corner
21 | self.disable = True
22 |
23 | if sys.platform.startswith("win"):
24 | self.overrideredirect(True)
25 | self.transparent_color = self._apply_appearance_mode(self._fg_color)
26 | self.attributes("-transparentcolor", self.transparent_color)
27 | elif sys.platform.startswith("darwin"):
28 | self.overrideredirect(True)
29 | self.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
30 | self.transparent_color = 'systemTransparent'
31 | self.attributes("-transparent", True)
32 | if not text_color:
33 | text_color = "black"
34 | else:
35 | self.attributes("-type", "splash")
36 | self.transparent_color = '#000001'
37 | self.corner = 0
38 | self.withdraw()
39 |
40 | self.disable = False
41 | self.fg_color = ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
42 | self.frame = CTkFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color, corner_radius=self.corner, border_width=2)
43 | self.frame.pack(expand=True, fill="both")
44 |
45 | self.attach = attach
46 | self.keywidth = keywidth
47 | self.keyheight = keyheight
48 | self.keycolor = self._apply_appearance_mode(ThemeManager.theme["CTkFrame"]["top_fg_color"]) if key_color is None else key_color
49 | self.textcolor = self._apply_appearance_mode(ThemeManager.theme["CTkLabel"]["text_color"]) if text_color is None else text_color
50 | self.hovercolor = self._apply_appearance_mode(ThemeManager.theme["CTkButton"]["hover_color"]) if hover_color is None else hover_color
51 | self.command = command
52 | self.resizable(width=False, height=False)
53 | self.transient(self.master)
54 |
55 | self.row1_1 = CTkFrame(self.frame)
56 | self.row2_1 = CTkFrame(self.frame)
57 | self.row3_1 = CTkFrame(self.frame)
58 | self.row4_1 = CTkFrame(self.frame)
59 | self.row5_1 = CTkFrame(self.frame)
60 |
61 | self.row1_2 = CTkFrame(self.frame)
62 | self.row2_2 = CTkFrame(self.frame)
63 | self.row3_2 = CTkFrame(self.frame)
64 | self.row4_2 = CTkFrame(self.frame)
65 | self.row5_2 = CTkFrame(self.frame)
66 |
67 | self.row1_1.grid(row=1, column=0, pady=(5,0))
68 | self.row2_1.grid(row=2, column=0, padx=5)
69 | self.row3_1.grid(row=3, column=0, padx=5)
70 | self.row4_1.grid(row=4, column=0)
71 | self.row5_1.grid(row=5, column=0, pady=(0,5))
72 |
73 | self._init_keys(**kwargs)
74 |
75 | # hide/show PopupKeyboard
76 | self.attach.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
77 | self.attach.bind('', lambda e: self._iconify(), add="+")
78 | self.bind('', lambda e: self.withdraw() if not self.disable else None, add="+")
79 |
80 | self.update_idletasks()
81 | self.x = x
82 | self.y = y
83 | self._iconify()
84 | self.attributes('-alpha', alpha)
85 |
86 | def _init_keys(self, **kwargs):
87 | self.keys = {
88 | 'row1' : ['1','2','3','4','5','6','7','8','9','0','◀'],
89 | 'row2' : ['Tab','q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p','\ '],
90 | 'row3' : ['⋀','a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',';','Enter'],
91 | 'row4' : ['z', 'x', 'c', 'v', 'b', 'n', 'm',',','.','?'],
92 | 'row5' : ['[',']','+',' space ','-','~',"'"]
93 | }
94 |
95 | self.keys2 = {
96 | 'row1' : ['!','@','#','$','%','^','&','*','(',')','◀'],
97 | 'row2' : ['Tab','Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P','|'],
98 | 'row3' : ['⋁','A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',':','Enter'],
99 | 'row4' : ['Z', 'X', 'C', 'V', 'B', 'N', 'M','<','>','/'],
100 | 'row5' : ['{','}','=',' space ','_','`','"']
101 | }
102 |
103 | for row in self.keys.keys():
104 | if row == 'row1':
105 | i = 1
106 | for k in self.keys[row]:
107 | Button(self.row1_1,
108 | text=k,
109 | width=self.keywidth,
110 | height=self.keyheight,
111 | bg=self.keycolor,
112 | fg=self.textcolor,
113 | highlightthickness=0,
114 | borderwidth=1,
115 | activebackground=self.hovercolor,
116 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
117 | i += 1
118 | i = 1
119 | for k in self.keys2[row]:
120 | Button(self.row1_2,
121 | text=k,
122 | width=self.keywidth,
123 | height=self.keyheight,
124 | bg=self.keycolor,
125 | fg=self.textcolor,
126 | highlightthickness=0,
127 | borderwidth=1,
128 | activebackground=self.hovercolor,
129 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
130 | i += 1
131 | elif row == 'row2':
132 | i = 2
133 | for k in self.keys[row]:
134 | Button(self.row2_1,
135 | text=k,
136 | width=self.keywidth,
137 | height=self.keyheight,
138 | bg=self.keycolor,
139 | fg=self.textcolor,
140 | highlightthickness=0,
141 | borderwidth=1,
142 | activebackground=self.hovercolor,
143 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
144 | i += 1
145 | i = 2
146 | for k in self.keys2[row]:
147 | Button(self.row2_2,
148 | text=k,
149 | width=self.keywidth,
150 | height=self.keyheight,
151 | bg=self.keycolor,
152 | fg=self.textcolor,
153 | highlightthickness=0,
154 | borderwidth=1,
155 | activebackground=self.hovercolor,
156 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
157 | i += 1
158 | elif row == 'row3':
159 | i = 2
160 | for k in self.keys[row]:
161 | Button(self.row3_1,
162 | text=k,
163 | width=self.keywidth,
164 | height=self.keyheight,
165 | bg=self.keycolor,
166 | fg=self.textcolor,
167 | highlightthickness=0,
168 | borderwidth=1,
169 | activebackground=self.hovercolor,
170 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
171 | i += 1
172 | i = 2
173 | for k in self.keys2[row]:
174 | Button(self.row3_2,
175 | text=k,
176 | width=self.keywidth,
177 | height=self.keyheight,
178 | bg=self.keycolor,
179 | fg=self.textcolor,
180 | highlightthickness=0,
181 | borderwidth=1,
182 | activebackground=self.hovercolor,
183 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
184 | i += 1
185 | elif row == 'row4':
186 | i = 2
187 | for k in self.keys[row]:
188 | Button(self.row4_1,
189 | text=k,
190 | width=self.keywidth,
191 | height=self.keyheight,
192 | bg=self.keycolor,
193 | fg=self.textcolor,
194 | highlightthickness=0,
195 | borderwidth=1,
196 | activebackground=self.hovercolor,
197 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
198 | i += 1
199 | i = 2
200 | for k in self.keys2[row]:
201 | Button(self.row4_2,
202 | text=k,
203 | width=self.keywidth,
204 | height=self.keyheight,
205 | bg=self.keycolor,
206 | fg=self.textcolor,
207 | highlightthickness=0,
208 | borderwidth=1,
209 | activebackground=self.hovercolor,
210 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
211 | i += 1
212 | else:
213 | i = 3
214 | for k in self.keys[row]:
215 | if k == ' space ':
216 | Button(self.row5_1,
217 | text=k,
218 | width=self.keywidth * 3,
219 | height=self.keyheight,
220 | bg=self.keycolor,
221 | fg=self.textcolor,
222 | highlightthickness=0,
223 | borderwidth=1,
224 | activebackground=self.hovercolor,
225 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
226 | else:
227 | Button(self.row5_1,
228 | text=k,
229 | width=self.keywidth,
230 | height=self.keyheight,
231 | bg=self.keycolor,
232 | fg=self.textcolor,
233 | highlightthickness=0,
234 | borderwidth=1,
235 | activebackground=self.hovercolor,
236 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
237 | i += 1
238 | i = 3
239 | for k in self.keys2[row]:
240 | if k == ' space ':
241 | Button(self.row5_2,
242 | text=k,
243 | width=self.keywidth * 3,
244 | height=self.keyheight,
245 | bg=self.keycolor,
246 | fg=self.textcolor,
247 | highlightthickness=0,
248 | borderwidth=1,
249 | activebackground=self.hovercolor,
250 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
251 | else:
252 | Button(self.row5_2,
253 | text=k,
254 | width=self.keywidth,
255 | height=self.keyheight,
256 | bg=self.keycolor,
257 | fg=self.textcolor,
258 | highlightthickness=0,
259 | borderwidth=1,
260 | activebackground=self.hovercolor,
261 | command=lambda k=k: self._attach_key_press(k), **kwargs).grid(row=0,column=i)
262 | i += 1
263 |
264 | self.up = False
265 | self.hide = False
266 |
267 | def destroy_popup(self):
268 | self.destroy()
269 | self.disable = True
270 |
271 | def _iconify(self):
272 | if self.disable: return
273 | if self.hide:
274 | self.deiconify()
275 | self.focus()
276 | self.hide = False
277 | self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x
278 | self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y
279 | self.geometry('{}x{}+{}+{}'.format(self.frame.winfo_reqwidth(),
280 | self.frame.winfo_reqheight(),
281 | self.x_pos,self.y_pos))
282 | else:
283 | self.withdraw()
284 | self.hide = True
285 |
286 | def _attach_key_press(self, k):
287 | if k == 'Enter':
288 | if self.command: self.command()
289 | self.withdraw()
290 |
291 | elif k == '⋁' or k =='⋀':
292 | if self.up:
293 | self.up = False
294 | self.row1_2.grid_forget()
295 | self.row2_2.grid_forget()
296 | self.row3_2.grid_forget()
297 | self.row4_2.grid_forget()
298 | self.row5_2.grid_forget()
299 |
300 | self.row1_1.grid(row=1, column=0, pady=(5,0))
301 | self.row2_1.grid(row=2, column=0, padx=5)
302 | self.row3_1.grid(row=3, column=0, padx=5)
303 | self.row4_1.grid(row=4, column=0)
304 | self.row5_1.grid(row=5, column=0, pady=(0,5))
305 | else:
306 | self.up = True
307 | self.row1_1.grid_forget()
308 | self.row2_1.grid_forget()
309 | self.row3_1.grid_forget()
310 | self.row4_1.grid_forget()
311 | self.row5_1.grid_forget()
312 |
313 | self.row1_2.grid(row=1, column=0, pady=(5,0))
314 | self.row2_2.grid(row=2, column=0, padx=5)
315 | self.row3_2.grid(row=3, column=0, padx=5)
316 | self.row4_2.grid(row=4, column=0)
317 | self.row5_2.grid(row=5, column=0, pady=(0,5))
318 | self.focus_set()
319 | self.update()
320 | elif k == ' space ':
321 | self.attach.insert(END, ' ')
322 | elif k == '◀':
323 | try:
324 | text = self.attach.get(0.0, END)
325 | self.attach.delete(0.0, END)
326 | self.attach.insert(0.0, text[:-2])
327 | except TypeError:
328 | text = self.attach.get()
329 | self.attach.delete(0, END)
330 | self.attach.insert(0, text[:-1])
331 |
332 | elif k == 'Tab':
333 | self.attach.insert(END, ' ')
334 | else:
335 | self.attach.insert(INSERT, k)
336 |
337 |
--------------------------------------------------------------------------------