2 @package libtextworker.interface.tk.editor
3 @brief Home of Tkinter(Ttk) text editors.
11 from hashlib
import md5
12 from tkinter
import BooleanVar, Menu, Text, Misc, TclError
13 from tkinter.font
import Font
14 from tkinter.ttk
import Scrollbar, Frame
15 from typing
import overload
18 from tklinenums
import TkLineNumbers
24 from libtextworker
import EDITOR_DIR
26 from .miscs
import CreateMenu
27 from ..
import stock_editor_configs
29 from ...get_config
import GetConfig
34 Customized Tkinter Text widget with some extra features.
35 Note: Use StyledTextControl._frame as the real StyledTextControl's parent.
39 Hash = md5(
"".encode(
"utf-8"))
41 def __init__(this, master: Misc |
None =
None, **kwds):
42 this._frame = Frame(master)
43 Text.__init__(this, this._frame, **kwds)
47 useMenu: bool =
False,
48 useScrollBars: bool =
True,
49 custom_config_path: str = EDITOR_DIR +
"/editor.ini",
53 Initialize the editor, libtextworker's customize part.
54 @param useMenu: Enable right-click menu (depends on the user setting - else defaults to disable)
55 @param useScrollBars: Show scroll bars
56 @param custom_config_path: Custom editor configs path (optional)
57 @param custom_theme_path: Custom editor theme path (optional)
58 @param tabwidth: Tab (\\t character) width (defaults to user setting else 4)
61 this.cfger =
GetConfig(stock_editor_configs, custom_config_path)
63 this.unRedo = this[
"undo"]
64 this.wrapbtn = BooleanVar(this)
66 if this.cfger.getkey(
"menu",
"enabled",
False,
True,
True):
67 useMenu = bool(this.cfger.getkey(
"menu",
"enabled"))
69 if int(this.cfger.getkey(
"indentation",
"size",
True,
True,
True)):
70 tabwidth = int(this.cfger.getkey(
"indentation",
"size"))
73 this.RMenu = Menu(this, tearoff=0)
75 this.addMenucascade = this.RMenu.add_cascade
76 this.addMenucheckbtn = this.RMenu.add_checkbutton
77 this.addMenucmd = this.RMenu.add_command
78 this.addMenuradiobtn = this.RMenu.add_radiobutton
79 this.addMenusepr = this.RMenu.add_separator
82 this.bind(
"<Button-3>", this._open_menu)
84 if useScrollBars
is True:
85 this._place_scrollbar()
88 if LINENUMS
and this.cfger.getkey(
"editor",
"line_count", noraiseexp=
True) \
89 in this.cfger.yes_values:
90 ln = TkLineNumbers(this._frame, this,
"center")
91 ln.pack(fill=
"y", side=
"left")
92 this.bind(
"<<Modified>>",
lambda evt: this._frame.after_idle(ln.redraw), add=
True)
95 def OnEditorModify(evt):
96 this.Hash = md5(this.get(1.0,
"end").encode(
"utf-8"))
97 this.bind(
"<<Modified>>", OnEditorModify, add=
True)
100 this.config(tabs=Font(font=this[
'font']).measure(
' '*tabwidth))
103 def _place_scrollbar(this):
104 xbar = Scrollbar(this._frame, orient=
"horizontal", command=this.xview)
105 ybar = Scrollbar(this._frame, orient=
"vertical", command=this.yview)
107 xbar.pack(side=
"bottom", fill=
"x")
108 ybar.pack(side=
"right", fill=
"y")
111 def _menu_init(this):
112 this.RMenu = CreateMenu(
116 "accelerator":
"Ctrl+X",
117 "handler":
lambda: this.event_generate(
"<Control-x>"),
121 "accelerator":
"Ctrl+C",
122 "handler":
lambda: this.event_generate(
"<Control-c>"),
126 "accelerator":
"Ctrl+V",
127 "handler":
lambda: this.event_generate(
"<Control-v>"),
132 this.RMenu.add_separator()
135 accelerator=
"Ctrl+Z",
136 command=
lambda: this.edit_undo(),
140 accelerator=
"Ctrl+Y",
141 command=
lambda: this.edit_redo(),
144 def _open_menu(this, event):
146 this.RMenu.post(event.x_root, event.y_root)
148 this.RMenu.grab_release()
154 Probably this will let you know if the editor content has been cooked or not.
156 def checkhash(target):
return not this.Hash.digest() == md5(target.encode(
"utf-8")).digest()
157 if not this.FileLoaded:
return checkhash(
"")
158 return checkhash(open(this.FileLoaded,
"r").read())
163 Warning: the path must exists on the file system, else
164 an exception will be raised (no handle from us).
165 Also this will OVERWRITE existing editor CONTENT, so make
166 sure you have your backup way.
168 content = open(path,
"r").read()
169 this.insert(1.0, content)
170 this.FileLoaded = path
171 this.Hash = md5(content.encode(
"utf-8"))
176 Write the current editor contents into a file.
178 content = this.get(1.0,
"end")
179 open(path,
"w").write(content)
180 this.Hash = md5(content.encode(
"utf-8"))
185 Write the current editor contents into the loaded file, if any.
186 If not able to, do nothing.
188 if this.FileLoaded:
return this.SaveFile(this.FileLoaded)
193 Toggle editor word wrap mode.
194 Only use with TextWidget.wrapbtn BooleanVar.
196 value = this.wrapbtn.get()
197 this.configure(wrap=
"none" if value
else "word")
198 this.wrapbtn.set(
not value)
204 Undoes the last edit option, if able to.
213 Redoes the last undone edit option, if able to.
221 TextWidget = StyledTextControl
def EditorInit(this, bool useMenu=False, bool useScrollBars=True, str custom_config_path=EDITOR_DIR+"/editor.ini", int tabwidth=4)
def LoadFile(this, str path)
bool wrapmode(this, event=None)
def SaveFile(this, str path)