class GetResourceApp(BaseWin): def __init__(self, tk_win): BaseWin.__init__(self, tk_win) self.input_url = StringVar() self.input_url.set('https://www.meijubie.com/movie/index44655.html') self.processing = False self.selectProcessor = StringVar() # 处理请求的个数 self.current_name = '' def set_init_window(self): self.tk_win.grid_rowconfigure(1, weight=1) self.tk_win.grid_columnconfigure(0, weight=1) labelframe = LabelFrame(text="输入") labelframe.grid_columnconfigure(1, weight=1) labelframe.grid(column=0, row=0, padx=10, pady=10, sticky=EW) Label(labelframe, text="目标网址", justify=LEFT, width=10).grid(row=0, column=0) entry = Entry(labelframe, textvariable=self.input_url) entry.grid(row=0, column=1, sticky=EW, padx=5) entry.bind('<Key-Return>', self.getResourceLink) self.button = Button(labelframe, text="获取", command=self.getResourceLink, width=10) self.button.grid(row=0, column=2, sticky=E, padx=5) Label(labelframe, text="处理器", justify=LEFT, width=10).grid(row=1, column=0) select_process = ttk.Combobox(labelframe, textvariable=self.selectProcessor, state="readonly", values=PROCESS_FUN_NAME_LIST, width=90) select_process.set("请选择网址处理器") select_process.grid(row=1, column=1, sticky=EW, columnspan=2, padx=5, pady=5) if len(PROCESS_FUN_NAME_LIST) > 0: select_process.set(PROCESS_FUN_NAME_LIST[0]) output_frame = LabelFrame(text="链接结果") output_frame.grid(column=0, row=1, sticky=NSEW, padx=10) self.copy_btn = Button(output_frame, text="复制到剪贴板", command=lambda: self.copy(self.output_txt)) self.copy_btn.config(state=DISABLED) self.copy_btn.pack() self.output_txt = ScrolledText(output_frame) self.output_txt.pack(side=TOP, expand=TRUE, fill=BOTH, padx=10, pady=10) self.output_txt.bind( "<Button-3>", lambda x: self.rightKey(x, self.output_txt)) # 绑定右键鼠标事件 self.output_txt.bind("<<Selection>>", self.on_text_selection) # 绑定选择事件 # self.output_txt.grid(column=0, columnspan=4) # self.vbar = ttk.Scrollbar(output_frame, orient=VERTICAL, command=self.output_txt.yview) # self.output_txt.configure(yscrollcommand=self.vbar.set) def on_text_selection(self, event=NONE): # try: # selection = self.output_txt.get(SEL_FIRST, SEL_LAST) # except Exception: # selection = NONE # if selection is not NONE and len(selection) > 0: if self.output_txt.tag_ranges("sel"): self.copy_btn.config(state=NORMAL) else: self.copy_btn.config(state=DISABLED) def getResourceLink(self, event=NONE): if self.processing: return self.processing = True # 创建进度条 self.gress_bar = GressBar() # 禁用按钮 self.button.state = DISABLED th = threading.Thread(target=self.doGetResourceLink) th.setDaemon(True) th.start() # 启动进度条 self.gress_bar.start() pass def doGetResourceLink(self): self.current_name = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) self.output_txt.insert( END, '#########################Begin:%s##########################\n' % self.current_name) request_url = self.input_url.get() if not request_url: self.output_txt.insert(END, '请求的网址为空\n') self.end_process() return self.output_txt.insert(END, request_url + '\n') self.output_txt.insert(END, '\n') processor = PROCESS_FUN_DICT[self.selectProcessor.get()] if processor is None: self.output_txt.insert(END, '未找到对应的网站处理器\n') self.end_process() return last_time = time.time_ns() try: results = processor.getDownloadLink(request_url) except Exception as e: self.output_txt.insert(END, '%s\n' % e.args) self.end_process() return index_start = self.output_txt.index("end-1c linestart") for item in results: self.output_txt.insert(END, item + '\n') index_end = self.output_txt.index("end-1c linestart") self.output_txt.insert( END, '\n耗时=%d毫秒\n' % ((time.time_ns() - last_time) / 1E6)) self.end_process(index_end, index_start) def end_process(self, index_end=END, index_start=END): self.output_txt.insert( END, '###########################End:%s##########################\n' % self.current_name) self.gress_bar.quit() self.button.state = NORMAL self.processing = False # 移除之前的选择 self.output_txt.tag_remove(SEL, '1.0', END) # 设置选中状态 print("\nstart=%s,end=%s\n" % (index_start, index_end)) self.output_txt.tag_add(SEL, index_start, index_end) self.output_txt.focus_set()
class myPanel(tkinter.Tk): def __init__(self): tkinter.Tk.__init__(self, 'lsx') self.withdraw() # 先withdraw隐藏再deiconify显示可使setCenter不会导致页面闪烁 self.title('电话簿v1.05 (联系作者:QQ11313213)') # TODO 是否有用 self.keys_var = tkinter.StringVar() self.tex1 = ScrolledText(self) ent1 = tkinter.Entry(self, textvariable=self.keys_var, width=125) ent1.pack(padx=2, pady=2, fill=tkinter.constants.BOTH, side=tkinter.constants.TOP) self.tex1.pack(padx=2, pady=2, fill=tkinter.constants.BOTH, expand=True) self.menu = tkinter.Menu(self, tearoff=0) self.menu.add_command(label='复制', command=self.copyItem) self.menu.add_separator() self.menu.add_command(label='来源') self.menu.add_separator() self.menu.add_command(label='刷新', command=readContacts2) self.menu.add_command(label='前后文', command=self.location) self.menu.add_separator() self.menu.add_command(label='导入文件', command=ImportFiles) self.menu.add_command(label='新增和更改', command=UpdateFile) self.menu0 = tkinter.Menu(self, tearoff=0) self.menu0.add_command(label='刷新', command=readContacts2) self.menu0.add_separator() self.menu0.add_command(label='导入文件', command=ImportFiles) self.menu0.add_command(label='新增和更改', command=UpdateFile) self.menu0.add_separator() submenu = [tkinter.Menu(self, tearoff=0)] self.menu0.add_cascade(label='Designed by Lsx. ', menu=submenu[0]) for key, value in [['Name', 'Li Shixian'], ['Mail', '*****@*****.**'], ['Website', 'github.com/znsoooo/contacts'], ['Wechat', 'Xian_2'], ['Donate', 'xxxx']]: submenu.append(tkinter.Menu(self, tearoff=0)) submenu.append(tkinter.Menu(self, tearoff=0)) submenu[-1].add_command(label=value) submenu[0].add_cascade(label=key, menu=submenu[-1]) self.img_wechat = tkinter.PhotoImage( data=Image.img1) # 没有self会导致显示图片为空白 self.img_donate = tkinter.PhotoImage(data=Image.img2) submenu[8].entryconfig(0, image=self.img_wechat) submenu[10].entryconfig(0, image=self.img_donate) submenu[0].add_separator() submenu[0].add_command(label='All Rights Reserved.', command=bonus) setCenter(self) self.deiconify() ent1.focus() ent1.bind('<KeyRelease>', self.onKeyRelease) self.tex1.bind('<ButtonRelease-3>', self.onRightClick) def select(self, row): self.tex1.mark_set('insert', '%d.0' % row) self.tex1.tag_remove('sel', '0.0', 'end') self.tex1.tag_add('sel', '%d.0' % row, '%d.0' % (row + 1)) def location(self, row=0): if not row: row = self.current_index + 1 self.onKeyRelease(keys='') self.select(row) self.tex1.see('%d.0' % row) def copyItem(self): text = self.tex1.selection_get() self.clipboard_clear() self.clipboard_append(text[:-1]) # 去掉文末换行符 def onRightClick(self, evt=0): self.tex1.focus() # 当焦点在ent1中时 self.current = int(self.tex1.index('current').split('.')[0]) if len(self.index): self.current_index = self.index[self.current - 1] line_last = 0 for line, file in file_list: if line > self.current_index: break else: line_last = line self.menu.entryconfig(2, label='来源: %s (line:%s)' % (file, self.current_index - line_last + 1)) self.menu.entryconfig( 2, command=lambda: os.popen('explorer /select, %s\\%s\\%s' % (os.getcwd(), DATA_FOLDER, file))) self.select(self.current) self.menu.post(evt.x_root, evt.y_root) else: self.menu0.post(evt.x_root, evt.y_root) return self.current def onKeyRelease(self, evt=0, keys=None): if keys is None: keys = self.keys_var.get() keys = keys.lower().split(' ') ss_new = [] self.index = [] for n, s in enumerate(ss): ok = True for key in keys: if key not in s[1]: ok = False if ok: ss_new.append(s[0]) self.index.append(n) # TODO 提出搜索部分到独立的函数 self.tex1.config(state='normal') self.tex1.delete('1.0', 'end') self.tex1.insert('1.0', '\n'.join(ss_new)) self.tex1.config(state='disabled') # 禁止编辑 self.title('电话簿v1.05 (联系作者:QQ11313213) - %s结果' % len(ss_new)) # title更改耗时短可以做到'同时'更改的效果 return ss_new
class Application(tkinter.Tk): def __init__(self): """Initialize widgets, methods.""" tkinter.Tk.__init__(self) self.grid() fontoptions = families(self) font = Font(family="Verdana", size=10) menubar = tkinter.Menu(self) fileMenu = tkinter.Menu(menubar, tearoff=0) editMenu = tkinter.Menu(menubar, tearoff=0) fsubmenu = tkinter.Menu(editMenu, tearoff=0) ssubmenu = tkinter.Menu(editMenu, tearoff=0) # adds fonts to the font submenu and associates lambda functions for option in fontoptions: fsubmenu.add_command(label=option, command = lambda: font.configure(family=option)) # adds values to the size submenu and associates lambda functions for value in range(1,31): ssubmenu.add_command(label=str(value), command = lambda: font.configure(size=value)) # adds commands to the menus menubar.add_cascade(label="File",underline=0, menu=fileMenu) menubar.add_cascade(label="Edit",underline=0, menu=editMenu) fileMenu.add_command(label="New", underline=1,command=self.new, accelerator="Ctrl+N") fileMenu.add_command(label="Open", command=self.open, accelerator="Ctrl+O") fileMenu.add_command(label="Save", command=self.save, accelerator="Ctrl+S") fileMenu.add_command(label="Exit", underline=1,command=exit, accelerator="Ctrl+Q") editMenu.add_command(label="Copy", command=self.copy, accelerator="Ctrl+C") editMenu.add_command(label="Cut", command=self.cut, accelerator="Ctrl+X") editMenu.add_command(label="Paste", command=self.paste, accelerator="Ctrl+V") editMenu.add_cascade(label="Font", underline=0, menu=fsubmenu) editMenu.add_cascade(label="Size", underline=0, menu=ssubmenu) editMenu.add_command(label="Color", command=self.color) editMenu.add_command(label="Bold", command=self.bold, accelerator="Ctrl+B") editMenu.add_command(label="Italic", command=self.italic, accelerator="Ctrl+I") editMenu.add_command(label="Underline", command=self.underline, accelerator="Ctrl+U") editMenu.add_command(label="Overstrike", command=self.overstrike, accelerator="Ctrl+T") editMenu.add_command(label="Undo", command=self.undo, accelerator="Ctrl+Z") editMenu.add_command(label="Redo", command=self.redo, accelerator="Ctrl+Y") self.config(menu=menubar) self.bind_all("<Control-n>", self.new) self.bind_all("<Control-o>", self.open) self.bind_all("<Control-s>", self.save) self.bind_all("<Control-q>", self.exit) self.bind_all("<Control-b>", self.bold) self.bind_all("<Control-i>", self.italic) self.bind_all("<Control-u>", self.underline) self.bind_all("<Control-T>", self.overstrike) self.bind_all("<Control-z>", self.undo) self.bind_all("<Control-y>", self.redo) self.text = ScrolledText(self, state='normal', height=80, wrap='word', font = font, pady=2, padx=3, undo=True) self.text.grid(column=0, row=0, sticky='NSEW') # Frame configuration self.grid_columnconfigure(0, weight=1) self.resizable(True, True) def new(self, *args): """Creates a new window.""" app = Application() app.title('Python Text Editor') app.option_add('*tearOff', False) app.mainloop() def color(self): """Changes selected text color.""" try: (rgb, hx) = tkinter.colorchooser.askcolor() self.text.tag_add('color', 'sel.first', 'sel.last') self.text.tag_configure('color', foreground=hx) except TclError: pass def bold(self, *args): """Toggles bold for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "bold" in current_tags: self.text.tag_remove("bold", "sel.first", "sel.last") else: self.text.tag_add("bold", "sel.first", "sel.last") bold_font = Font(self.text, self.text.cget("font")) bold_font.configure(weight="bold") self.text.tag_configure("bold", font=bold_font) except TclError: pass def italic(self, *args): """Toggles italic for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "italic" in current_tags: self.text.tag_remove("italic", "sel.first", "sel.last") else: self.text.tag_add("italic", "sel.first", "sel.last") italic_font = Font(self.text, self.text.cget("font")) italic_font.configure(slant="italic") self.text.tag_configure("italic", font=italic_font) except TclError: pass def underline(self, *args): """Toggles underline for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "underline" in current_tags: self.text.tag_remove("underline", "sel.first", "sel.last") else: self.text.tag_add("underline", "sel.first", "sel.last") underline_font = Font(self.text, self.text.cget("font")) underline_font.configure(underline=1) self.text.tag_configure("underline", font=underline_font) except TclError: pass def overstrike(self, *args): """Toggles overstrike for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "overstrike" in current_tags: self.text.tag_remove("overstrike", "sel.first", "sel.last") else: self.text.tag_add("overstrike", "sel.first", "sel.last") overstrike_font = Font(self.text, self.text.cget("font")) overstrike_font.configure(overstrike=1) self.text.tag_configure("overstrike", font=overstrike_font) except TclError: pass def undo(self, *args): """Undo function""" try: self.text.edit_undo() except TclError: pass def redo(self, *args): """Redo function""" try: self.text.edit_redo() except TclError: pass def copy(self, *args): """Copy text""" self.clipboard_clear() self.clipboard_append(self.text.selection_get()) def cut(self, *args): """Cut text""" self.copy self.text.delete("sel.first", "sel.last") def paste(self, *args): """Paste text""" insertion = self.selection_get(selection = "CLIPBOARD") self.text.insert(0.0, insertion) def open(self, *args): """Opens a file dialog to open a plain text file.""" filename = tkinter.filedialog.askopenfilename() with open(filename) as f: text = f.read() self.text.delete("1.0", "end") self.text.insert('insert', text) def save(self, *args): try: """Opens a file dialog to save the text in plain text format.""" text = self.text.get("1.0", "end") filename = tkinter.filedialog.asksaveasfilename() with open(filename, 'w') as f: f.write(text) except FileNotFoundError: pass def exit(self, *args): """Exits the program.""" self.quit()
class textEditor: def __init__(self): self.root = Tk() self.root.title('My Text Editor') self.text = ScrolledText(self.root, width=100, height=50) # if using .grid(), the area of text will be on the left when in full screen mode. self.text.pack() menubar = Menu(self.root) filemenu = Menu(menubar) menubar.add_cascade(label="File", menu=filemenu) filemenu.add_command(label="New", command=self.new_file) filemenu.add_command(label="Open", command=self.open_file) filemenu.add_command(label="Save", command=self.save_file) filemenu.add_separator() filemenu.add_command(label="Exit", command=self.exit) editmenu = Menu(menubar) menubar.add_cascade(label="Edit", menu=editmenu) fontmenu = Menu(menubar) menubar.add_cascade(label="Font", menu=fontmenu) fontmenu.add_command(label="Courier", command=self.font_courier) fontmenu.add_command(label="Helvetica", command=self.font_helvetica) fontmenu.add_command(label="Times", command=self.font_times) fontmenu.add_command(label="Roman", command=self.font_roman) menubar.add_command(label="Find", command=self.find_pattern) self.root.config(menu=menubar) self.root.mainloop() def save_file(self): data = self.text.get("1.0", "end-1c") savelocation = filedialog.asksaveasfilename(defaultextension='.txt', title='Save Text') with open(savelocation, "w+") as f: try: f.write(data) except: messagebox.showerror(title="Oops!", message="Unable to save it...") def font_times(self): self.text.config(font=("times", 12)) def font_courier(self): self.text.config(font=("Courier", 15)) def font_helvetica(self): self.text.config(font=("Helvetica", 12)) def font_roman(self): self.text.config(font=("Times New Roman", 12, "bold")) def open_file(self): f = filedialog.askopenfile(mode='r') t = f.read() self.text.delete(0.0, END) self.text.insert(0.0, t) def new_file(self): if len(self.text.get('1.0', END + '-1c')) > 0: ask_for_save = messagebox.askquestion( 'save', 'do you want to save the file') if ask_for_save == 'yes': self.save_file() else: self.text.delete(0.0, END) self.root.title('My Text Editor') def exit(self): ask_for_exit = messagebox.askquestion( "Exit", "Are you sure you want to exit?") if ask_for_exit == 'yes': self.root.destroy() def handle_click(self): self.text.tag_config('Found', background='white', foreground='red') def find_pattern(self): self.text.tag_remove("Found", '1.0', END) find = simpledialog.askstring("Find....", "Enter text:") # prevent frozen because of inputing nothing in find. if not find: return idx = '0.0' if find: idx = '1.0' while 1: idx = self.text.search(find, idx, nocase=1, stopindex=END) if not idx: break lastidx = '%s+%dc' % (idx, len(find)) self.text.tag_add('Found', idx, lastidx) idx = lastidx self.text.bind("<1>", self.handle_click()) data = self.text.get('1.0', END) occurance = data.upper().count(find.upper()) if occurance > 1: label = messagebox.showinfo( "Find", find + " has occurances " + str(occurance) + " times.") elif occurance == 1: label = messagebox.showinfo( "Find", find + " has just occurances " + str(occurance) + " time.") else: label = messagebox.showinfo("Find", "No results")
class textEditor(): alltabs = None def __init__(self, window, labelFrame, tabs, vocab, startWithSameLetter, tabsOpen, file_path=""): self.window = window # record the directory path of the file self.file_path = file_path if file_path: self.file_name = self.file_path else: # if the file path doesn't exist, name it accordingly self.file_name = 'Untitled' # record the necessary passed-in parameters self.labelFrame = labelFrame self.tabsOpen = tabsOpen self.tabs = tabs self.vocab = vocab self.startWithSameLetter = startWithSameLetter # create the main gui elements in the respective tab frame self.notepad = ScrolledText(self.labelFrame, font=("Calibri", 15)) editorbox = self.notepad self.var = tk.IntVar() self.autoCorrectOption = tk.Checkbutton(self.labelFrame, \ text="Enable Auto-Correct", variable=self.var, command=self.switchSpellChecker) self.autoComplete_suggestions = tk.Listbox(self.labelFrame) self.autoCorrect_suggestions = tk.Listbox(self.labelFrame) myFont = Font(family="Calibri", size=15) self.autoComplete_suggestions.configure(font=myFont) self.autoCorrect_suggestions.configure(font=myFont) # create funtionality bars inside tab frame self.createMenuBar() self.createToolBar(self.labelFrame) self.autoCorrectOption.grid(row=1, column=9) self.notepad.config(undo=True) self.notepad.config(height=900) self.notepad.grid(row=2, column=0, columnspan=11, sticky="WE") self.window.protocol("WM_DELETE_WINDOW", lambda: newFileTab.closeCheck(self.tabsOpen)) # add pre-set markup on the entire text widget in the tab frame self.notepad.tag_configure("misspelling", foreground="red", underline=True) # bind all navigation to checking the spelling of the word self.nav_click = self.notepad.bind("<ButtonRelease-1>", self.spellChecker) self.nav_up = self.notepad.bind("<Up>", self.spellChecker) self.nav_down = self.notepad.bind("<Down>", self.spellChecker) self.nav_left = self.notepad.bind("<Left>", self.spellChecker) self.nav_right = self.notepad.bind("<Right>", self.spellChecker) # check each word's spelling after typed and mark it up self.notepad.bind("<space>", self.markUp) self.notepad.bind(".", self.markUp) # keep calling autocomplete while user is writing for letter in "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM": self.notepad.bind("<KeyRelease-" + letter + ">", self.autoComplete) self.notepad.bind("<KeyRelease-BackSpace>", self.autoComplete) # bind file shortcuts self.notepad.bind('<Control-s>', newFileTab.saveToFile) self.notepad.bind('<Control-o>', newFileTab.openFile) self.notepad.bind('<Control-n>', newFileTab.createFile) self.notepad.bind('<Control-c>', newFileTab.copySelected) self.notepad.bind('<Control-x>', newFileTab.cutSelected) self.notepad.bind('<Control-v>', newFileTab.pasteClipboard) # this function creates the top menu bar including all functionalities def createMenuBar(self): menuBar = Menu(self.window) # create drop-down options for the file menu fileMenu = tk.Menu(menuBar, tearoff=0) fileMenu.add_command( label="New Document", command=lambda: newFileTab.createFile(self.tabsOpen)) fileMenu.add_command( label="Open Local File", command=lambda: newFileTab.openFile(self.tabsOpen)) fileMenu.add_command( label="Save file", command=lambda: newFileTab.saveToFile(self.tabsOpen)) fileMenu.add_separator() fileMenu.add_command( label="Close File", command=lambda: newFileTab.closeFile(self.tabsOpen)) fileMenu.add_command(label="Exit", command=lambda: newFileTab.quit(self.tabsOpen)) menuBar.add_cascade(label="File", menu=fileMenu) # create drop-down options for the edit menu editMenu = tk.Menu(menuBar, tearoff=0) editMenu.add_command( label="Undo", command=lambda: newFileTab.undoEdit(self.tabsOpen)) editMenu.add_command( label="Redo", command=lambda: newFileTab.redoEdit(self.tabsOpen)) editMenu.add_command( label="Copy", command=lambda: newFileTab.copySelected(self.tabsOpen)) editMenu.add_command( label="Cut", command=lambda: newFileTab.cutSelected(self.tabsOpen)) editMenu.add_command( label="Paste", command=lambda: newFileTab.pasteClipboard(self.tabsOpen)) menuBar.add_cascade(label="Edit", menu=editMenu) self.window.config(menu=menuBar) '''icon pics retrieved from: https://icons-for-free.com/folder+open+icon-1320161390409087972/''' # this function creates the tool bar with clickable icon shortcuts for the functionalities def createToolBar(self, labelFrame): # add icon for handling creating new files new_img = tk.PhotoImage(file="newicon.png") new_img = new_img.zoom(1) new_img = new_img.subsample(15) # add icon for handling opening local files open_img = tk.PhotoImage(file="openicon.png") open_img = open_img.zoom(1) open_img = open_img.subsample(15) # add icon for handling saving files save_img = tk.PhotoImage(file="saveicon.png") save_img = save_img.zoom(1) save_img = save_img.subsample(4) # add icon for handling copying from files copy_img = tk.PhotoImage(file="copyicon.png") copy_img = copy_img.zoom(1) copy_img = copy_img.subsample(4) # add icon for handling cutting from files cut_img = tk.PhotoImage(file="cuticon.png") cut_img = cut_img.zoom(1) cut_img = cut_img.subsample(4) # add icon for handling cutting from clipboard paste_img = tk.PhotoImage(file="pasteicon.png") paste_img = paste_img.zoom(1) paste_img = paste_img.subsample(4) # add icon for handling undo edits undo_img = tk.PhotoImage(file="undoicon.png") undo_img = undo_img.zoom(1) undo_img = undo_img.subsample(4) # add icon for handling redo edits redo_img = tk.PhotoImage(file="redoicon.png") redo_img = redo_img.zoom(1) redo_img = redo_img.subsample(4) # add icon for handling closing current file tab close_img = tk.PhotoImage(file="closeicon.png") close_img = close_img.zoom(1) close_img = close_img.subsample(4) # create all respective buttons and configure them to their appropriate icons and function calls new_button = tk.Button( labelFrame, image=new_img, command=lambda: newFileTab.createFile(self.tabsOpen)) open_button = tk.Button( labelFrame, image=open_img, command=lambda: newFileTab.openFile(self.tabsOpen)) save_button = tk.Button( labelFrame, image=save_img, command=lambda: newFileTab.saveToFile(self.tabsOpen)) copy_button = tk.Button( labelFrame, image=copy_img, command=lambda: newFileTab.copySelected(self.tabsOpen)) cut_button = tk.Button( labelFrame, image=cut_img, command=lambda: newFileTab.cutSelected(self.tabsOpen)) paste_button = tk.Button( labelFrame, image=paste_img, command=lambda: newFileTab.pasteClipboard(self.tabsOpen)) undo_button = tk.Button( labelFrame, image=undo_img, command=lambda: newFileTab.undoEdit(self.tabsOpen)) redo_button = tk.Button( labelFrame, image=redo_img, command=lambda: newFileTab.redoEdit(self.tabsOpen)) close_button = tk.Button( labelFrame, image=close_img, command=lambda: newFileTab.closeFile(self.tabsOpen)) new_button.image = new_img open_button.image = open_img save_button.image = save_img copy_button.image = copy_img cut_button.image = cut_img paste_button.image = paste_img undo_button.image = undo_img redo_button.image = redo_img close_button.image = close_img # grid the buttons appropriately onto the tab frame new_button.grid(row=1, column=1) open_button.grid(row=1, column=2) save_button.grid(row=1, column=3) copy_button.grid(row=1, column=4) cut_button.grid(row=1, column=5) paste_button.grid(row=1, column=6) undo_button.grid(row=1, column=7) redo_button.grid(row=1, column=8) close_button.grid(row=1, column=10) # this function takes automatically the first choice from the suggestion # box and replaces it with the word that is underlined as misspelt def autoCorrect(self, event): lastWord = self.getLastWord() if self.spellCheckerList(lastWord): # get first suggestiosn from listbox contents bestSuggestion = self.spellCheckerList(lastWord)[0] # configure and find first and last index of word to be replaced start = self.notepad.get('1.0', tk.END).index(lastWord) end = start + len(lastWord) line_num = int(float(self.notepad.index(tk.CURRENT))) start_i = str(line_num) + '.' + str(start) end_i = str(line_num) + '.' + str(end) # delete the misspelled word by the best suggestion in text widget self.notepad.delete(start_i, end_i) self.notepad.insert(start_i, bestSuggestion) # this function unbinds the arrows with the list from the dictionary # so that the list dosen't appear when pressing the auto-correct option def switchSpellChecker(self): self.notepad.unbind('<ButtonRelease-1>') self.notepad.unbind('<Up>') self.notepad.unbind('<Down>') self.notepad.unbind('<Left>') self.notepad.unbind('<Right>') self.notepad.unbind("<space>") # if the autocorrect option is pressed if self.var.get(): # replace the spellchecker bindings to autocorrect self.notepad.bind("<space>", self.autoCorrect) self.notepad.bind(".", self.autoCorrect) # if it is not pressed else: # rebind the orginal keys for the spellchecker listbox functionality self.notepad.bind("<ButtonRelease-1>", self.spellChecker) self.notepad.bind("<Up>", self.spellChecker) self.notepad.bind("<Down>", self.spellChecker) self.notepad.bind("<Left>", self.spellChecker) self.notepad.bind("<Right>", self.spellChecker) # check each word's spelling after typed and mark it up self.notepad.bind("<space>", self.isSpeltCorrect) self.notepad.bind(".", self.isSpeltCorrect) # this function gets the last word that was typed by the user def getLastWord(self): # split all input text at all white space characters (e.g. space, tab, enter) wordsList = re.split("\s+", self.notepad.get("1.0", tk.END)) # remove last empty string wordsList.remove('') # last word is at the last index of the words list lastWord = wordsList[len(wordsList) - 1] # remove unnecessary punctuations next to the last word lastWord_stripped = lastWord.translate( str.maketrans('', '', string.punctuation)) return lastWord_stripped.lower() # here we edit the words that are misspelt after # getting the words from the screen and correct their form def spellCheckerList(self, word_to_check): edits = {} # if there's no word selected or a space is selected if word_to_check == "" or word_to_check == " ": return # if the word is misspelt, record its respective edit distances and frequencies elif not self.isSpeltCorrect(word_to_check): # compute for min edit distance from each word in dictionary for word in self.vocab: edits_num = self.minEditDistance(word_to_check, word) # record all words corresponding to edits numbers 1 and 2 in a dictionary if edits_num <= 2: # if there is a key in the dictionary corresponding to the same edit distance if edits_num in edits: # add it to its list of values edits[edits_num].append(word) else: # if not, create a new key for the number of edits and add it edits[edits_num] = [word] # record and sort frequencies of words corresponding for 1 edit and 2 edits freqs1 = [] freqs2 = [] # sorting words with edit distance 1 one based on frequency if 1 in edits: for similar_word in edits.get(1): # record frequency of each word with the same edit distance freq = self.vocab.get(similar_word) freqs1.append(freq) # sorting words with edit distance 1 one based on frequency if 2 in edits: for similar_word in edits.get(2): # record frequency of each word with the same edit distance freq = self.vocab.get(similar_word) freqs2.append(freq) # rearrange frequencies individually freqs1.sort() freqs2.sort() # combine the two frequency lists in order of 1 then 2 to get appropriate suggestions list # the smallest edit distance if the first priority, then its frequency freqs = freqs1 + freqs2 suggestions = [] for f in freqs: for word in self.vocab: # get words based on their corresponding frequencies in order if self.vocab.get(word) == f: # add each corresponding word to the suggestions list suggestions.append(word) return suggestions '''STILL TO DO, CONSIDER CAPITALIZATION IN SPELLCHECKER and replacement word/pop-up lists''' # this function checks if the word is spelt correctly or not # it returns a boolean value based on this condition def isSpeltCorrect(self, word): if word in self.vocab: return True return False # this functions recognizes the word and finds similar words # depending on their frequency, this will be then added to the # suggestion list. this function updates after typing each charachter def autoCompleteList(self, e): typed_word = self.getCurrWord(e).lower() if typed_word == "": return freqs = [] suggestions = [] inp_length = len(typed_word) for word in self.vocab: # check for english words that start with the same characters if word[:inp_length].lower() == typed_word: print('hi') # record the frequency ranks of such words freq = self.vocab.get(word) freqs.append(freq) # order frequencies freqs.sort() for f in freqs: for word in self.vocab: # get words based on their corresponding frequencies in order if self.vocab.get(word) == f: suggestions.append(word) return suggestions # this function takes the list of words suggested if any and # inserts them on the screen in the autocomplete suggestions listbox def autoComplete(self, event): self.autoComplete_suggestions.destroy() self.autoComplete_suggestions = tk.Listbox(window) myFont = Font(family="Calibri", size=15) self.autoComplete_suggestions.configure(font=myFont) word = self.getCurrWord(event).lower() # ignore autocomplete call if the word is empty if not word: return # if there is one character typed if len(word) == 1: # use pre-loaded dictionary to get suggestiosn into listbox suggestions = self.startWithSameLetter.get(word) i = 0 # add the first 10 word suggestions, as long as they exist while i < 11 and i < len(suggestions): for l in suggestions: # add them to the suggestions listbox in order self.autoComplete_suggestions.insert(i, l + " ") i += 1 else: # if typed portion is a part of a valid word if self.autoCompleteList(event): # get autocomplete list and append its first 10 values into the listbox suggestions = self.autoCompleteList(event)[:10] for i in range(len(suggestions)): self.autoComplete_suggestions.insert( i, suggestions[i] + " ") # if not, indicate lack of matches on listbox else: self.autoComplete_suggestions.insert(0, "No matches found.") # remove duplicate words in the suggestions listbox and the typed word if word in self.autoComplete_suggestions.get(0, tk.END): index = self.autoComplete_suggestions.get(0, tk.END).index(word) # delete duplicate word from suggestions listbox self.autoComplete_suggestions.delete(index) # if there are more suggestions available after 10, add the next one if len(self.autoCompleteList(event)) >= 11: self.autoComplete_suggestions.insert( 10, self.autoCompleteList(event)[10] + " ") # place the listbox where the typing cursor is (x, y, w, h) = self.notepad.bbox('insert') self.autoComplete_suggestions.place(x=x + 140, y=y + 200, anchor="center") self.autoComplete_suggestions.bind('<<ListboxSelect>>', self.autoCompleteClickSelect) # this function also draws a list box with all the suggested words that # could replace the misspelt word. def spellChecker(self, event): self.autoComplete_suggestions.destroy() self.autoCorrect_suggestions.destroy() self.autoCorrect_suggestions = tk.Listbox(self.labelFrame) myFont = Font(family="Calibri", size=15) self.autoCorrect_suggestions.configure(font=myFont) # if the selected word is the one being currently typed # autocomplete it and don't spellcheck it (word not fully typed yet) '''if self.getCurrWord(event) and self.getNavigWord(event): self.autoComplete(event) return''' word = self.getNavigWord(event) # if the suggestions listbox is not empty, clear it if len(self.autoCorrect_suggestions.get(0, tk.END)) != 0: self.autoCorrect_suggestions.delete(0, tk.END) # exit spell checker if the word is spelt correctly if self.isSpeltCorrect(word): return # if current word is not empty and is spelled incorrectly elif len(self.notepad.get('1.0', 'end-1c')) != 0: if self.spellCheckerList(word): # append first 10 suggestions into listbox suggestions = self.spellCheckerList(word)[:10] for i in range(len(suggestions)): self.autoCorrect_suggestions.insert(i, suggestions[i]) else: # if not close matches from min edit function, display appropriate message self.autoCorrect_suggestions.insert(0, "No matches found.") self.autoCorrect_suggestions.insert(1, "Add word to dictionary") if len(word) != 1: # place the listbox where the cursor is (x, y, w, h) = self.notepad.bbox('insert') self.autoCorrect_suggestions.place(x=x + 115, y=y + 160, anchor="center") self.autoComplete_suggestions = tk.Listbox(self.labelFrame) myFont = Font(family="Calibri", size=15) self.autoComplete_suggestions.configure(font=myFont) self.autoCorrect_suggestions.bind('<<ListboxSelect>>', self.autoCorrectClickSelect) # this function takes the selection that the user made from the suggestion box # and overwrites the word in he screen def autoCorrectClickSelect(self, event): selected_word = self.autoCorrect_suggestions.get( self.autoCorrect_suggestions.curselection()) # get the entire word the cursor is on navigWord = self.getNavigWord(event) if selected_word == "No matches found.": self.autoCorrect_suggestions.destroy() return elif selected_word == "Add word to dictionary": self.vocab[navigWord] = len(self.vocab) + 1 else: start = self.notepad.get('1.0', tk.END).index(navigWord) end = start + len(navigWord) line_num = int(float(self.notepad.index(tk.CURRENT))) # configure start and end indices of the word to be corrected syntax correctly start_i = str(line_num) + '.' + str(start) end_i = str(line_num) + '.' + str(end) # delete the misspelled word and replace it by the correct one selected from the listbox self.notepad.delete(start_i, end_i) self.notepad.insert(start_i, selected_word) if self.autoCorrect_suggestions.winfo_exists: self.autoCorrect_suggestions.destroy() # this function takes the selection that the user made from the suggestion box # and overwrites the word in the screen for the autocomplete option def autoCompleteClickSelect(self, event): if self.autoComplete_suggestions.curselection(): selected_word = self.autoComplete_suggestions.get( self.autoComplete_suggestions.curselection()) if selected_word == "No matches found.": self.autoComplete_suggestions.destroy() return # get the partial word currently being typed currWord = self.getCurrWord(event).lower() # configure start and end indices of the word to be corrected syntax correctly start = self.notepad.get('1.0', tk.END).index(currWord) end = start + len(currWord) line_num = int(float(self.notepad.index(tk.CURRENT))) start_i = str(line_num) + '.' + str(start) end_i = str(line_num) + '.' + str(end) # delete the misspelled word and replace it by the correct one selected from the listbox self.notepad.delete(start_i, end_i) self.notepad.insert(start_i, selected_word) self.autoComplete_suggestions.destroy() # this function underlines the word that is misspelt and # colors it with red def markUp(self, misspelt_word): lastWord = self.getLastWord() # if word contains numbers of special characters, don't mark it up if not lastWord.isalpha(): return self.autoComplete_suggestions.destroy() # search for starting index of the misspelt word index = self.notepad.search(r'\s', "insert", backwards=True, regexp=True) if index == "": index = "1.0" else: index = self.notepad.index("%s+1c" % index) word = self.notepad.get(index, "insert").translate( str.maketrans('', '', string.punctuation)) # if word spelled correctly, remove pre-set misspelling tag if word.lower() in self.vocab: self.notepad.tag_remove("misspelling", index, "%s+%dc" % (index, len(word))) else: self.notepad.tag_add("misspelling", index, "%s+%dc" % (index, len(word))) '''modfiied code from: https://stackoverflow.com/questions/3732605/add-advanced-features-to-a-tkinter-text-widget''' # This function finds the minimum edit distance using a modified version of the Levistein algorithm # This is my own implementation of the algorithm def minEditDistance(self, misspelt_word, vocab_word): rows = len(misspelt_word) + 1 columns = len(vocab_word) + 1 matrix = [] # split list of lists based on rows # initialize values for column contents for each row for i in range(rows): matrix.append([]) for j in range(columns): matrix[i].append(-1) # empty string row first_row = [] for n in range(columns): first_row.append(n) matrix = [first_row] + matrix[1:] # add first column values in matrix n = 0 for i in range(rows): matrix[i][0] = n n += 1 # for each letter of the misspelt word for r in range(rows - 1): # go through each letter in the vocab word for c in range(columns - 1): # if the letters are the same if vocab_word[c] == misspelt_word[r]: # copy down the value at the relative left diagonal position in the matrix # into the corresponding matrix position of the current string comparison matrix[r + 1][c + 1] = matrix[r][c] # if letters are different else: # take the minimum value of the three upper left diagonals to the current position adj_min = min(matrix[r][c], matrix[r][c + 1], matrix[r + 1][c]) # add 1 to get the minimum additional edit to transform the two strings parts so far # add resulting value into corresponding matrix position matrix[r + 1][c + 1] = adj_min + 1 # minimum number of edits is the last computed value of the matrix minEdits = matrix[rows - 1][columns - 1] return minEdits # this function gets the word that the cursor is hovering over in the text widget # and returns it. def getNavigWord(self, event): start = self.notepad.index("insert wordstart") end = self.notepad.index("insert wordend") nav_word = self.notepad.get(start, end) # remove unnecessary punctuations next to the typed word nav_word_stripped = nav_word.translate( str.maketrans('', '', string.punctuation)) return nav_word_stripped.lower() # this function gets the word that is being modified currently from the user # and returns it. def getCurrWord(self, event): all_typed = self.notepad.get("1.0", "end") i = all_typed.rfind(" ") curr_word = all_typed[i + 1:].strip() # remove unnecessary punctuations next to the typed word curr_word_stripped = curr_word.translate( str.maketrans('', '', string.punctuation)) return curr_word_stripped.lower()
class Application(tkinter.Tk): def __init__(self): """Initialize widgets, methods.""" tkinter.Tk.__init__(self) self.grid() fontoptions = families(self) font = Font(family="Verdana", size=10) menubar = tkinter.Menu(self) fileMenu = tkinter.Menu(menubar, tearoff=0) editMenu = tkinter.Menu(menubar, tearoff=0) fsubmenu = tkinter.Menu(editMenu, tearoff=0) ssubmenu = tkinter.Menu(editMenu, tearoff=0) # adds fonts to the font submenu and associates lambda functions for option in fontoptions: fsubmenu.add_command(label=option, command = lambda: font.configure(family=option)) # adds values to the size submenu and associates lambda functions for value in range(1,31): ssubmenu.add_command(label=str(value), command = lambda: font.configure(size=value)) # adds commands to the menus menubar.add_cascade(label="File",underline=0, menu=fileMenu) menubar.add_cascade(label="Edit",underline=0, menu=editMenu) fileMenu.add_command(label="New", underline=1, command=self.new, accelerator="Ctrl+N") fileMenu.add_command(label="Open", command=self.open, accelerator="Ctrl+O") fileMenu.add_command(label="Save", command=self.save, accelerator="Ctrl+S") fileMenu.add_command(label="Exit", underline=1, command=exit, accelerator="Ctrl+Q") editMenu.add_command(label="Copy", command=self.copy, accelerator="Ctrl+C") editMenu.add_command(label="Cut", command=self.cut, accelerator="Ctrl+X") editMenu.add_command(label="Paste", command=self.paste, accelerator="Ctrl+V") editMenu.add_cascade(label="Font", underline=0, menu=fsubmenu) editMenu.add_cascade(label="Size", underline=0, menu=ssubmenu) editMenu.add_command(label="Color", command=self.color) editMenu.add_command(label="Bold", command=self.bold, accelerator="Ctrl+B") editMenu.add_command(label="Italic", command=self.italic, accelerator="Ctrl+I") editMenu.add_command(label="Underline", command=self.underline, accelerator="Ctrl+U") editMenu.add_command(label="Overstrike", command=self.overstrike, accelerator="Ctrl+T") editMenu.add_command(label="Undo", command=self.undo, accelerator="Ctrl+Z") editMenu.add_command(label="Redo", command=self.redo, accelerator="Ctrl+Y") self.config(menu=menubar) """Accelerator bindings. The cut, copy, and paste functions are not bound to keyboard shortcuts because Windows already binds them, so if Tkinter bound them as well whenever you typed ctrl+v the text would be pasted twice.""" self.bind_all("<Control-n>", self.new) self.bind_all("<Control-o>", self.open) self.bind_all("<Control-s>", self.save) self.bind_all("<Control-q>", self.exit) self.bind_all("<Control-b>", self.bold) self.bind_all("<Control-i>", self.italic) self.bind_all("<Control-u>", self.underline) self.bind_all("<Control-T>", self.overstrike) self.bind_all("<Control-z>", self.undo) self.bind_all("<Control-y>", self.redo) self.text = ScrolledText(self, state='normal', height=30, wrap='word', font = font, pady=2, padx=3, undo=True) self.text.grid(column=0, row=0, sticky='NSEW') # Frame configuration self.grid_columnconfigure(0, weight=1) self.resizable(True, True) """Command functions. *args is included because the keyboard bindings pass two arguments to the functions, while clicking in the menu passes only 1.""" def new(self, *args): """Creates a new window.""" app = Application() app.title('Python Text Editor') app.option_add('*tearOff', False) app.mainloop() def color(self): """Changes selected text color.""" try: (rgb, hx) = tkinter.colorchooser.askcolor() self.text.tag_add('color', 'sel.first', 'sel.last') self.text.tag_configure('color', foreground=hx) except TclError: pass def bold(self, *args): """Toggles bold for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "bold" in current_tags: self.text.tag_remove("bold", "sel.first", "sel.last") else: self.text.tag_add("bold", "sel.first", "sel.last") bold_font = Font(self.text, self.text.cget("font")) bold_font.configure(weight="bold") self.text.tag_configure("bold", font=bold_font) except TclError: pass def italic(self, *args): """Toggles italic for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "italic" in current_tags: self.text.tag_remove("italic", "sel.first", "sel.last") else: self.text.tag_add("italic", "sel.first", "sel.last") italic_font = Font(self.text, self.text.cget("font")) italic_font.configure(slant="italic") self.text.tag_configure("italic", font=italic_font) except TclError: pass def underline(self, *args): """Toggles underline for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "underline" in current_tags: self.text.tag_remove("underline", "sel.first", "sel.last") else: self.text.tag_add("underline", "sel.first", "sel.last") underline_font = Font(self.text, self.text.cget("font")) underline_font.configure(underline=1) self.text.tag_configure("underline", font=underline_font) except TclError: pass def overstrike(self, *args): """Toggles overstrike for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "overstrike" in current_tags: self.text.tag_remove("overstrike", "sel.first", "sel.last") else: self.text.tag_add("overstrike", "sel.first", "sel.last") overstrike_font = Font(self.text, self.text.cget("font")) overstrike_font.configure(overstrike=1) self.text.tag_configure("overstrike", font=overstrike_font) except TclError: pass def undo(self, *args): """Undo function""" try: self.text.edit_undo() except TclError: pass def redo(self, *args): """Redo function""" try: self.text.edit_redo() except TclError: pass def copy(self, *args): """Copy text""" self.clipboard_clear() self.clipboard_append(self.text.selection_get()) def cut(self, *args): """Cut text""" self.copy self.text.delete("sel.first", "sel.last") def paste(self, *args): """Paste text""" insertion = self.selection_get(selection = "CLIPBOARD") self.text.insert(0.0, insertion) def open(self, *args): """Opens a file dialog to open a plain text file.""" filename = tkinter.filedialog.askopenfilename() with open(filename) as f: text = f.read() self.text.delete("1.0", "end") self.text.insert('insert', text) def save(self, *args): try: """Opens a file dialog to save the text in plain text format.""" text = self.text.get("1.0", "end") filename = tkinter.filedialog.asksaveasfilename() with open(filename, 'w') as f: f.write(text) except FileNotFoundError: pass def exit(self, *args): """Exits the program.""" self.quit()
class LogView(ttk.Frame): """ Widget for the program log messages, ttk Frame implementation. :ivar List[MessageType] message_types: the MessageTypes to include in the filter combobox :ivar List[MessageType] all_types: a list of all the message types :ivar List[MessageDelay] message_delays: a list of all of the message delays :ivar Dict[str: collections.deque] messages: mapping of message type title string to dequeues of fixed size containing messages' body :ivar Dict[str: float] message_time: message body without timestamp mapped to time in s :ivar Dict[str: MessageDelay] message_time_filer: message body without timestamp mapped to MessageDelay :ivar float first_click_time: the time the header text was first clicked (Used for developer messages) :ivar int num_clicks: the number of times the header text was clicked within the specified time (Used for dev msgs) :ivar bool showing: True if developer messages are in the filter, False otherwise :ivar MessageType current_filter: the current message type to filter messages :ivar ttk.Combobox filter_box: the combobox tkinter widget used for selecting a filter level :ivar ttk.ScrolledText log_view: the scrolled text tkinter widget used for displaying log messages """ def __init__(self, container: ttk.Frame, **kwargs): """ Setup the log view widgets, and the data structures for storing messages. :param container: parent frame :param kwargs: additional arguments for the Frame """ super().__init__(container, **kwargs) self.message_types = [ MessageType.INFO, MessageType.WARNING, MessageType.ERROR ] self.all_types = self.message_types + [ MessageType.CRITICAL, MessageType.DEVELOPER ] self.message_delays = [ MessageDelay.FIRST, MessageDelay.SHORT, MessageDelay.LONG, MessageDelay.MAX ] self.messages = {} self.message_time = SizeDict(maxsize=1000) # type: Dict[str, float] self.message_time_filter = SizeDict( maxsize=1000) # type: Dict[str, MessageDelay] self.first_click_time = time.time() self.num_clicks = 0 self.showing = False self.current_filter = MessageType.INFO for t in self.all_types: self.messages[t.name.title()] = collections.deque(maxlen=25000) self.pack() header_frame = ttk.Frame(self) header_frame.pack(expand=True, fill=tk.BOTH) header_lbl = ttk.Label(header_frame, text="Program Log") header_lbl.pack(anchor=tk.W, side=tk.LEFT) header_lbl.bind("<Button-1>", self.click_handler) self.filter_box = ttk.Combobox( header_frame, values=[_type.name.title() for _type in self.message_types]) self.filter_box.config(state="readonly") self.filter_box.set(MessageType.INFO.name.title()) self.filter_box.pack(anchor=tk.E, side=tk.RIGHT) self.filter_box.bind("<<ComboboxSelected>>", self.filter_msg) self.filter_box.pack(anchor=tk.E) self.log_view = ScrolledText(self, wrap=tk.WORD, background=LOG_BACKGROUND_COLOR) for t in self.all_types: self.log_view.tag_configure(t.name, font=Font(family="Helvetica", size=10), foreground=t.color) self.log_view.pack(expand=True, fill=tk.BOTH) ttk.Button(self, text="Export", command=self.export).pack(anchor=tk.CENTER) def click_handler(self, _): """Click handler for the Program Log header to allow the user to view developer messages.""" if time.time() - self.first_click_time > 15.: self.first_click_time = time.time() self.num_clicks = 0 else: self.num_clicks += 1 if self.num_clicks == 10: self.num_clicks = 0 if not self.showing: self.message_types.append(MessageType.DEVELOPER) else: self.message_types.remove(MessageType.DEVELOPER) self.showing = not self.showing self.filter_box.config( values=[_type.name.title() for _type in self.message_types]) def export(self): """Export the messages to a log text file in the log folder.""" t = time.time() timestamp = datetime.datetime.fromtimestamp(t).strftime( "%Y%m%dT%H%M%S") if not os.path.isdir("log"): os.mkdir("log") with open(os.path.join("log", "{}_log.txt".format(str(timestamp))), "w") as f: _type = MessageType.INFO.filter_num if MessageType.DEVELOPER in self.message_types: _type = MessageType.DEVELOPER.filter_num msgs = self.get_msgs(_type) msgs_sorted = sort_messages(msgs) for t, (text, tag) in msgs_sorted: f.write(text) def clear(self): """Clear the log view scrolled text.""" self.log_view.config(state='normal') for t in self.all_types: try: self.log_view.tag_remove(t.name, "1.0", tk.END) except tk.TclError: pass self.log_view.delete("1.0", tk.END) def add_msg(self, msg: Message): """ Add the msg to the log view, and the message store data structures. :param msg: message to add to the log view """ self.message_time[msg.msg] = msg.time self.messages[msg.type.name.title()].append((msg.time, msg.text)) if msg.type.filter_num <= self.current_filter.filter_num: self.write_msg(msg.text, msg.type.name) def write_msg(self, text: str, tag: str, start: str = "1.0"): """ Write the text to the Scrolled Text tkinter widget, color it based on the tag, add it to the beginning if start is '1.0', if start tk.END add it to the end. :param text: text to write to the scrolled text view :param tag: name of the tag that defines the style for the text, based on MessageType :param start: the position to start writing the text """ self.log_view.config(state='normal') self.log_view.insert(start, text) self.highlight_pattern(text, tag) self.log_view.config(state='disabled') def highlight_pattern(self, pattern: str, tag: str, start: str = "1.0", end: str = "end", regexp: bool = False): """ Apply the given tag to all text that matches the given pattern. :param pattern: pattern to match when looking to format the text using the tag. :param tag: tkinter scrolled text tag corresponding to a tk font :param start: where to start looking for the pattern from :param end: where to end looking for the pattern :param regexp: If True, pattern will be treated as a Tcl regular expression """ start = self.log_view.index(start) end = self.log_view.index(end) self.log_view.mark_set("matchStart", start) self.log_view.mark_set("matchEnd", start) self.log_view.mark_set("searchLimit", end) count = tk.IntVar() while True: index = self.log_view.search(pattern, "matchEnd", "searchLimit", count=count, regexp=regexp) if index == "": break if count.get() == 0: break self.log_view.mark_set("matchStart", index) self.log_view.mark_set("matchEnd", "%s+%sc" % (index, count.get())) self.log_view.tag_add(tag, "matchStart", "matchEnd") def get_msgs(self, filter_num: int) -> collections.OrderedDict: """ Get all of the messages that have a filter number less than or equal the filter number. :param filter_num: the number used for filtering messages :return: time mapped to (text, tag) for all filtered messages """ msgs = {} for _type in self.all_types: if _type.filter_num <= filter_num: for msg in self.messages[_type.name.title()]: msgs[msg[0]] = (msg[1], _type.name) return collections.OrderedDict(msgs.items(), key=lambda t: t[0]) def filter_msg(self, _): """Filters messages when the filter combobox value is changed.""" for t in self.all_types: selection = self.filter_box.current() if t.name.title() == self.message_types[selection].name.title(): self.current_filter = t msgs = self.get_msgs(self.current_filter.filter_num) self.clear() msgs_sorted = sort_messages(msgs) for t, (text, tag) in msgs_sorted: self.write_msg(text, tag, start=tk.END)
class PyNote: def __init__(self,root): self.root = root self.root.title('Untitled - PyNote') self.root.configure( bg='lightgray') self.root.geometry('950x600') self.root.protocol("WM_DELETE_WINDOW", self.callback) self.root.minsize(width=700, height=400) # important stuffs ... self.font_style = ('arial',14) self.filename = None self.file_saved = True self.index=0 # widgets ,........ self.textarea = ScrolledText(self.root,font=self.font_style,undo=True, wrap = WORD) #self.textarea.bindtags(('Text','post-class-bindings', '.', 'all')) self.textarea.pack(fill=BOTH,expand=True) self.shortcut_binding() self.status = StringVar() self.pos = StringVar() self.status.set('PyNote - ({})'.format('Untitled File')) # important stuffs ... font_style = ('arial', 13) self.label = Label(self.root, textvariable=self.status, fg='black', bg='lightgray', anchor=SW, font=font_style) self.label.pack(side=LEFT,fill=BOTH) # cursor postion label... self.cursor_pos_lbl = Label(self.root, textvariable=self.pos, fg='black', bg='lightgray', anchor=NW, font=font_style) self.cursor_pos_lbl.pack(side=RIGHT) # code for popup menu bar .... # creating menu-bar ... self.m = Menu(root, tearoff=0) self.m.add_command(label="Refresh", command=self.refresh) self.m.add_command(label = "Select All",command=self.select_all) self.m.add_command(label="Cut", command=lambda: self.textarea.event_generate("<<Cut>>")) self.m.add_command(label="Copy", command=lambda: self.textarea.event_generate("<<Copy>>")) self.m.add_command(label = "Paste", command=lambda: self.textarea.event_generate("<<Paste>>")) self.m.add_command(label="Delete", command=lambda: self.textarea.delete(SEL_FIRST, SEL_LAST)) self.m.add_command(label="Add Date/Time", command=self.add_time_date) # Calling functions and other class methods or creating objects ...... self.menubar = Menubar(self) # function definations ................. def set_title(self,name= None): if name: self.root.title(name + " - PyNote") else: self.root.title('Untitled - PyNote') def new_file(self, event = None): self.textarea.delete(1.0, END) # call the save command if any text is written on text area .. self.filename = None self.set_title() self.update_status('PyNote - ({})'.format('Untitled File')) def open_file(self, *args): self.filename = filedialog.askopenfilename( defaultextension = ".txt", filetypes = [("All Files" , "*.*"), ("Text Files" , "*.txt"), ("Python Scripts" , "*.py"), ('HTML Docs.' , "*.html"), ("CSS Docs.","*.css")]) if self.filename: self.textarea.delete(1.0,END) with open(self.filename , "r") as f : self.textarea.insert(1.0,f.read()) self.update_status('File is Opened / ' + self.filename) self.set_title(self.filename) def save_file(self, *args): if self.filename: try: textarea_data = self.textarea.get(1.0,END) with open(self.filename, 'w') as f: f.write(textarea_data) self.update_status('File is Saved Again ') self.file_saved = True except Exception as e: messagebox.showerror('PyNote -Says ','Error Occurs '+ str(e)) else: self.save_as_file() def save_as_file(self, *args): try: new_file = filedialog.asksaveasfilename(initialfile = 'Untitled.txt', defaultextension=".txt", filetypes=[("All Files", "*.*"), ("Text Files", "*.txt"), ("Python Scripts", "*.py"), ('HTML Docs.', "*.html"), ("CSS Docs.", "*.css")] ) textarea_data = self.textarea.get(1.0,END) with open(new_file , 'w') as f: f.write(textarea_data) self.filename = new_file self.set_title(self.filename) self.file_saved = True self.update_status('File is Saved As '+ self.filename) except Exception as e: messagebox.showerror('PyNote -Says ','Error Occurs '+ str(e)) def about_us(self): messagebox.showinfo('About - Us',''' PyNote is a Text Editor which is looks like a Notepad \n PyNote is Build as a Demo Project for those Who are \n interested in building Awesome GUI Projects using Tkinter\n _________________________________________________________\n PyNote Developer : Yogesh Singh \n Build For : Dynamic Coding \n''') def about_pynote(self): messagebox.showinfo('About - PyNote',''' Current Version : 0.0 \n ''') def shortcut_binding(self): self.textarea.bind('<Control-n>',self.new_file) self.textarea.bind('<Control-Key-o>',self.open_file) self.textarea.bind('<Control-s>',self.save_file) self.textarea.bind('<Control-S>',self.save_as_file) self.textarea.bind('<Control-q>',self.close_App) self.textarea.bind('<Key>', self.text_area_cursor) #self.textarea.bind('<KeyPress>', self.cursor_pos) self.root.bind("<Button-3>", self.do_popup) def do_popup(self, event): try: self.m.tk_popup(event.x_root, event.y_root) finally: self.m.grab_release() def find_word_window(self): top = Toplevel(self.root) top.geometry('400x130') top.title('PyNote - Find Text ') top.resizable(0,0) # important stuffs ........... find_var = StringVar() self.total_var =StringVar() # --------------------------find window widgets ---------------------- self.find_entry = ttk.Entry(top,width=20,font=('times',12), textvariable = find_var) self.find_entry.focus_set() self.find_entry.bind('<Return>',self.find) self.find_entry.place(x=10,y=25) find_btn = Button(top,text='Find',width=10,bd=2,relief=RIDGE,command=self.find) find_btn.place(x=200,y=25) clear_btn = Button(top, text='Clear', width=10, bd=2, relief=RIDGE,command=lambda:find_var.set('')) clear_btn.place(x=300, y=25) self.total_world_count = Label(top,font=('arial',13,'bold')) self.total_world_count.place(x=20,y=80) top.mainloop() def find(self, *args): # remove tag 'found' from index 1 to END self.textarea.tag_remove('found', '1.0', END) # returns to widget currently in focus s = self.find_entry.get() if (s): idx = '1.0' self.count=0 while 1: # searches for desried string from index 1 idx = self.textarea.search(s, idx, nocase=1, stopindex=END) self.count +=1 if not idx: break # last index sum of current index and # length of text lastidx = '% s+% dc' % (idx, len(s)) # overwrite 'Found' at idx self.textarea.tag_add('found', idx, lastidx) idx = lastidx # mark located string as red self.textarea.tag_config('found', foreground='green', background='yellow') #self.textarea.focus_set() self.total_world_count['text'] = 'Total Word Count is : ' + str(self.count -1 ) def refresh(self): self.textarea.tag_delete("found") def find_replace_window(self): top = Toplevel(self.root) top.geometry('400x130') top.title('PyNote - Find Text ') top.focus_force() top.resizable(0, 0) # important stuffs ........... find_var = StringVar() replace_var = StringVar() # --------------------------find replace window widgets ---------------------- self.find_entry = ttk.Entry(top, width=20, font=('times', 12), textvariable=find_var) self.find_entry.focus_set() self.find_entry.bind('<Return>', self.find) self.find_entry.place(x=10, y=25) self.replace_entry = ttk.Entry(top, width=20, font=('times', 12), textvariable=replace_var) #self.replace_entry.focus_set() self.replace_entry.bind('<Return>', self.find_replace) self.replace_entry.place(x=10, y=60) find_btn = Button(top, text='Find', width=10, bd=2, relief=RIDGE, command=self.find) find_btn.place(x=200, y=25) replace_btn = Button(top, text='Replace', width=10, bd=2, relief=RIDGE, command=self.find_replace) replace_btn.place(x=200, y=60) clear_btn = Button(top, text='Clear All', width=10,height=3, bd=2, relief=RIDGE, command=lambda: [find_var.set(''),replace_var.set('')]) clear_btn.place(x=300, y=26) self.total_world_count = Label(top, text='',font=('arial', 13, 'bold')) self.total_world_count.place(x=20, y=100) top.mainloop() def find_replace(self, *args): find = self.find_entry.get() replace = self.replace_entry.get() if(find and replace): idx = '1.0' while 1: # searches for desired string from index 1 idx = self.textarea.search(find, idx, nocase=1, stopindex=END) print(idx) if not idx: break # last index sum of current index and # length of text lastidx = '% s+% dc' % (idx, len(find)) self.textarea.delete(idx, lastidx) self.textarea.insert(idx, replace) lastidx = '% s+% dc' % (idx, len(replace)) # overwrite 'Found' at idx self.textarea.tag_add('found', idx, lastidx) idx = lastidx self.total_world_count['text'] = '" {} " is replaced with " {} "'.format(find,replace) self.replace_entry.focus_set() def close_App(self, *args): print(self.textarea.get(1.0,END)) if self.textarea.get(1.0,END) != '' and self.file_saved == True: if messagebox.askyesno('PyNote Says','Do you really want to exit'): self.root.quit() else: val = messagebox.askyesnocancel('PyNote - Says', 'Save File Before Exit App. ') if val: self.save_file() self.root.destroy() elif val == False: self.root.destroy() def text_area_cursor(self, *args): #print('inside function ') self.file_saved = False if self.filename: self.update_status('PyNote - Currently Working on : ({})'.format(self.filename)) else: self.update_status('Untitled File') pos = self.textarea.index(INSERT) line , column = pos.split('.') column = int(column) + 1 #print('Current line is : ',line) #print('Current Column is : ', column) self.pos.set('Line : {} Column : {}'.format(line,column)) def cursor_pos(self, *args): pos = self.textarea.index(INSERT) line, column = pos.split('.') column = int(column) + 1 self.pos.set('Line : {} Column : {}'.format(line, column)) def update_status(self,data=''): self.status.set('PyNote - ({})'.format(data)) def select_font(self): font= askfont() # font is "" if the user has cancelled if font: # spaces in the family name need to be escaped font['family'] = font['family'].replace(' ', '\ ') font_str = "%(family)s %(size)i %(weight)s %(slant)s" % font if font['underline']: font_str += ' underline' if font['overstrike']: font_str += ' overstrike' self.font_style = font_str self.textarea.configure(font=self.font_style) def add_time(self): now = datetime.datetime.now() now = now.strftime(' Time: %I:%M:%S:%p ') self.textarea.insert(END, now) def add_date(self): now = datetime.datetime.now() now = now.strftime(' %Y-%m-%d ') self.textarea.insert(END, now) def add_time_date(self): now = datetime.datetime.now() now = now.strftime(' Date: %Y-%m-%d Time: %I:%M:%S:%p ') self.textarea.insert(END,now) # Select all the text in textbox def select_all(self, *args): self.textarea.tag_add(SEL, "1.0", END) self.textarea.mark_set(INSERT, "1.0") self.textarea.see(INSERT) def callback(self): if(self.file_saved) and (self.textarea.get(1.0,END)) != '': if messagebox.askokcancel("Quit", "Do you really wish to quit?"): self.root.destroy() else: val = messagebox.askyesnocancel('PyNote - Says','Save File Before Exit App. ') if val: self.save_file() elif val== False: self.root.destroy()
class NoteEditor: def __init__(self): self.id = None self.page_name = None self.font_name = 'arial' self.font_size = 12 self.font_weight = tk.NORMAL self.editor = None self.file_io = FileIO() self.syntax_file_io = FileIO() def create_editor(self, master): self.editor = ScrolledText(master, undo=True, autoseparators=True, maxundo=-1) # Styling of text area self.set_editor_font(None, None) self.editor.pack(side="left") self.editor.focus() self.editor.pack(fill="both", expand=True) # Configuring style tags self.editor.tag_config("BACKGROUND", background="yellow") self.editor.tag_configure("HIGHLIGHT", foreground="red") self.editor['wrap'] = tk.NONE self.editor.bind('<Button-3>', self.rClicker, add='') def set_editor_font(self, font_name, font_size, font_weight=None): if font_name is not None: self.font_name = font_name if font_size is not None and int(font_size) > 0: self.font_size = font_size if font_weight is not None: self.font_weight = font_weight self.editor['font'] = Font(family=self.font_name, size=self.font_size, weight=self.font_weight) def set_editor_bgcolor(self, hex_color): self.editor['background'] = hex_color def set_editor_fgcolor(self, hex_color): self.editor['foreground'] = hex_color def set_emphasis(self, on): if on == 1: bold_font = Font(family=self.font_name, size=self.font_size, weight="bold") self.editor.tag_configure("BOLDFONT", font=bold_font) if self.editor.tag_ranges(tk.SEL): self.editor.tag_add("BOLDFONT", tk.SEL_FIRST, tk.SEL_LAST) else: self.editor.tag_add("BOLDFONT", "1.0", tk.END) else: self.editor.tag_remove("BOLDFONT", "1.0", tk.END) def toggle_wrap(self, on): if on == 1: self.editor['wrap'] = tk.WORD else: self.editor['wrap'] = tk.NONE def search_forward(self, text): located_start = self.editor.search(text, tk.INSERT, stopindex=tk.END, forwards=True, nocase=True) located_end = '{}+{}c'.format(located_start, len(text)) if located_start is '' or located_end is '': return False self.select_editor_location(located_start, located_end) # Start position is moved after current found location. self.editor.mark_set(tk.INSERT, located_end) return True def search_backward(self, text): located_start = self.editor.search(text, tk.INSERT, stopindex='1.0', backwards=True, nocase=True) located_end = '{}+{}c'.format(located_start, len(text)) if located_start is '' or located_end is '': return False self.select_editor_location(located_start, located_end) # Start position is moved after current found location. self.editor.mark_set(tk.INSERT, located_start) return True def replace_selected_text(self, new_text): self.editor.delete('sel.first', 'sel.last') self.editor.insert('insert', new_text) def select_editor_location(self, selection_start, selection_end): print('Found location start: ', selection_start) print('Found location end: ', selection_end) selection_start_float = float(selection_start) self.editor.tag_remove(tk.SEL, "1.0", 'end') self.editor.tag_add(tk.SEL, selection_start, selection_end) self.editor.focus_force() self.editor.see(selection_start_float) def is_dirty(self): return self.editor.edit_modified() def rClicker(self, e): ''' right click context menu for all Tk Entry and Text widgets ''' try: def rClick_Copy(e, apnd=0): e.widget.event_generate('<Control-c>') def rClick_Cut(e): e.widget.event_generate('<Control-x>') def rClick_Paste(e): e.widget.event_generate('<Control-v>') def rClick_Highlight_Keyword(e): self.highlight_syntax(True) e.widget.focus() nclst = [ (' Cut', lambda e=e: rClick_Cut(e)), (' Copy', lambda e=e: rClick_Copy(e)), (' Paste', lambda e=e: rClick_Paste(e)), (' Highlight Keyword', lambda e=e: rClick_Highlight_Keyword(e)), ] rmenu = tk.Menu(None, tearoff=0, takefocus=0) for (txt, cmd) in nclst: rmenu.add_command(label=txt, command=cmd) rmenu.tk_popup(e.x_root + 40, e.y_root + 10, entry="0") except tk.TclError: print ' - rClick menu, something wrong' pass return "break" def highlight_syntax(self, enable=True): syntax_file = Path(Path( self.file_io.file_name).suffix[1:]).with_suffix('.hs') hs_config_data = Configuration.get_hs_configuration(syntax_file) if hs_config_data is None: print('No syntax file ', syntax_file) return #print(hs_config_data) keywords = hs_config_data['keyword']['tags'] keyword_fgcolor = hs_config_data['keyword']['color'] constant_fgcolor = hs_config_data['constant']['color'] numbers = re.findall(r'\d{1,3}', self.file_io.file_data) self.editor.tag_config('tg_kw', foreground=keyword_fgcolor) self.editor.tag_config('tg_num', foreground=constant_fgcolor) #keywords = ['package', 'public', 'private', 'abstract', 'internal', 'new', 'static', 'final', 'long', 'extends', # 'class', 'import', 'null', 'for', 'if', 'return', 'int', 'char', 'float', 'double', 'implements'] for keyword in keywords: self.editor.mark_set(tk.INSERT, '1.0') while True: located_end = self.highlight_keyword(keyword + ' ', 'tg_kw') if located_end == 0: break self.editor.mark_set(tk.INSERT, located_end) self.editor.mark_set(tk.INSERT, '1.0') for each_number in numbers: located_end = self.highlight_keyword(each_number, 'tg_num') if located_end != 0: self.editor.mark_set(tk.INSERT, located_end) print("Syntax highlight executed.") def highlight_keyword(self, text, tag): located_start = self.editor.search(text, tk.INSERT, stopindex=tk.END, forwards=True, nocase=False) located_end = '{}+{}c'.format(located_start, len(text)) print(located_start, ',', located_end) print('keyword', text) if located_start is '' or located_end is '': return 0 self.editor.tag_add(tag, located_start, located_end) return located_end