class Highlighter(QSyntaxHighlighter): def __init__(self, parent): QSyntaxHighlighter.__init__(self, parent) self.formatter = Formatter() self.lexer = PythonLexer() self.style = {} for token, style in self.formatter.style: char_format = QTextCharFormat() if style['color']: char_format.setForeground(QColor("#" + style['color'])) if style['bgcolor']: char_format.setBackground(QColor("#" + style['bgcolor'])) if style['bold']: char_format.setFontWeight(QFont.Bold) if style['italic']: char_format.setFontItalic(True) if style['underline']: char_format.setFontUnderline(True) char_format.setFontStyleHint(QFont.Monospace) self.style[token] = char_format def highlightBlock(self, text): position = self.currentBlock().position() length = self.currentBlock().length() text = str(self.document().toPlainText()[position:position+length]) tokens = self.lexer.get_tokens(text) i = 0 for token, text in tokens: self.setFormat(i, len(text), self.style[token]) i += len(text)
class Interface(object): SCREEN_WIDTH = 640 # int(window.winfo_screenwidth()) SCREEN_HEIGHT = 480 # int(window.winfo_screenheight()) _libraryArray = [] _ldArray = [] _deviceArray = [] advancedMode = 0 # lists of menu items (not all of these are used) commandList = ["if", "elif", "else", "for", "while"] advancedCommandList = ["break", "continue"] expressionList = ["+", "-", "*", "/", "//", "%", "**"] advancedExpressionList = ["<<", ">>", "|", "^", "&", "~", "@"] equationList = ["==", "!=", "<", "<=", ">", ">="] advancedEquationList = ["is", "is not", "in", "not in"] functionList = ["print()", "input()", "str()", "int()", "float()", "round()", "range()", "len()", "min()", "max()"] functionList2 = ["time.sleep()", "time.time()", "random.random()", "random.randint()"] logicGateList = ["AND", "OR", "XOR", "NOT"] advancedLogicGateList = ["NAND", "NOR", "XNOR"] variableList = ["i", "j", "k", "l"] selectedPart = None moveList = [] fileName = "" fullScreen = 0 def __init__(self, parent): self.lexer = PythonLexer() # Used for syntax highlighting self._window = tk.Frame(parent) self.helpText = tk.StringVar(self._window) # setup window self._window.master.geometry(str(self.SCREEN_WIDTH) + "x" + str(self.SCREEN_HEIGHT)) if self.fullScreen: self._window.master.attributes("-fullscreen", True) self._window.bind("<F11>", self.swapFullScreen) self._window.master.title(ld.windowName) # setup library imports (and localisation data of those libraries) libReader = configparser.ConfigParser() libReader.optionxform = str configIni = "config.ini" libReader.read(configIni) for i in libReader["LIBRARIES"]: if libReader["LIBRARIES"][i] == "1": self._libraryArray.append(i) module_name = i file_path = "lib/" + i + "/" + i + ".py" spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) ld_module_name = i + "Localisationdata" ld_file_path = "lib/" + i + "/" + ld_module_name + ".py" ld_spec = importlib.util.spec_from_file_location(ld_module_name, ld_file_path) ld_module = importlib.util.module_from_spec(ld_spec) ld_spec.loader.exec_module(ld_module) self._ldArray.append(ld_module) for objname in dir(module): if type(eval("module." + objname)) is type: self._deviceArray.append(getattr(module, objname)()) # setup menu bar self.menuBar = tk.Menu(self._window) # setup file menu self.fileMenu = tk.Menu(self.menuBar, tearoff=0) for i in ld.fileMenuList: self.fileMenu.add_command(label=i, command=lambda item=i: self.fileClick(item)) self.fileMenu.add_checkbutton(label=ld.advanced, variable=self.advancedMode, onvalue=1, offvalue=0, command=self.setAdvancedMode) self.menuBar.add_cascade(label=ld.fileWindowName, menu=self.fileMenu) # setup command menu self.commandMenu = tk.Menu(self.menuBar, tearoff=0) for i in self.commandList: self.commandMenu.add_command(label=i, command=lambda item=i: self.commandClick(item)) self.menuBar.add_cascade(label=ld.commandWindowName, menu=self.commandMenu) # setup expression menu self.expressionMenu = tk.Menu(self.menuBar, tearoff=0) for i in self.expressionList: self.expressionMenu.add_command(label=i, command=lambda item=i: self.expressionClick(item)) self.menuBar.add_cascade(label=ld.expressionWindowName, menu=self.expressionMenu) # setup equation menu self.equationMenu = tk.Menu(self.menuBar, tearoff=0) for i in self.equationList: self.equationMenu.add_command(label=i, command=lambda item=i: self.equationClick(item)) self.menuBar.add_cascade(label=ld.equationWindowName, menu=self.equationMenu) # setup standard function menu self.functionMenu = tk.Menu(self.menuBar, tearoff=0) for i in self.functionList: self.functionMenu.add_command(label=i, command=lambda item=i: self.functionClick(item)) self.menuBar.add_cascade(label=ld.functionWindowName, menu=self.functionMenu) # setup standard function menu self.functionMenu2 = tk.Menu(self.menuBar, tearoff=0) for i in self.functionList2: self.functionMenu2.add_command(label=i, command=lambda item=i: self.function2Click(item)) self.menuBar.add_cascade(label=ld.functionWindow2Name, menu=self.functionMenu2) # setup device menu # basically, this code imports all selected libraries, checks their code for all public variables (i.e. instances of parts), # checks those instance variables for all public methods (i.e. movement directions), # translates all names and organises all this data neatly in a cascading menu. self.deviceMenu = tk.Menu(self.menuBar, tearoff=0) for libraryCounter in range(len(self._libraryArray)): library = self._libraryArray[libraryCounter] device = self._deviceArray[libraryCounter] locData = self._ldArray[libraryCounter] specificPartMenu = tk.Menu(self.deviceMenu, tearoff=0) for part in vars(device): if not part.startswith('_'): specificMoveMenu = tk.Menu(specificPartMenu, tearoff=0) partPointer = getattr(device, part) for movement in dir(partPointer): if callable(getattr(partPointer, movement)) and not movement.startswith("_"): specificMoveMenu.add_command(label=locData.partDictionary[movement], command=lambda lib_=library, device_=device, part_=part, move_=movement: self.moveClick(lib_, device_, part_, move_)) specificPartMenu.add_cascade(label=locData.partDictionary[part], menu=specificMoveMenu) self.deviceMenu.add_cascade(label=library, menu=specificPartMenu) self.menuBar.add_cascade(label=ld.connectedDeviceWindowName, menu=self.deviceMenu) # setup textbox self.textBox = scrolledtext.ScrolledText(self._window, width=self.SCREEN_WIDTH // 8 - 3, height=self.SCREEN_HEIGHT // 20) self.textBox.grid(row=2, columnspan=6) # setup run button self.runButton = tk.Button(self._window, text=ld.runButtonText, command=self.runCode) self.runButton.grid(row=3, sticky='w') # setup help text self.helpText.set(ld.helpInfo+ld.helpInfoDefault) self.helpLabel = tk.Label(self._window, textvar=self.helpText) self.helpLabel.grid(sticky=tk.W, row=4, column=0, columnspan=6) # start program loop self._window.master.config(menu=self.menuBar) self._window.grid(row=0, column=0) self._window.master.bind("<KeyRelease-Return>", self.recolorize) # Toggles fullscreen def swapFullScreen(self, *_args): self.fullScreen ^= 1 if self.fullScreen: self._window.master.attributes("-fullscreen", True) else: self._window.master.attributes("-fullscreen", False) # Setup what happens when file menu is clicked def fileClick(self, item): itemId = ld.fileMenuList.index(item) if itemId == 0: # new file self.textBox.delete(1.0, tk.END) self.fileName = "" if itemId == 1: # open file self.openFile() if itemId == 2: # save file self.saveFile() if itemId == 3: # save as self.saveFile(True) # Run the code in the textbox def runCode(self): exec(self.textBox.get("1.0", tk.END)) # Open a file def openFile(self): # Shows file selection popup fileOpenPopup = filedialog.askopenfile(parent=self._window, mode="r", initialdir=os.getcwd() + "/saves") if fileOpenPopup is None: return # Opens file to textbox. Old data gets deleted. self.fileName = fileOpenPopup.name file = open(self.fileName, "r") self.textBox.delete(1.0, tk.END) self.textBox.insert(1.0, file.read()) file.close() self.recolorize() # Saves the file def saveFile(self, newName=False): # If the file isn't named yet or "Save as..." is clicked, the user gets a popup to choose a filename and location. if newName | (self.fileName == ""): fileSavePopup = filedialog.asksaveasfile(parent=self._window, mode="w", initialdir=os.getcwd() + "/saves", defaultextension=".py", filetypes=(("python files", "*.py"), ("text files", "*.txt"))) if fileSavePopup is None: return self.fileName = fileSavePopup.name # Saves textbox to that file file = open(self.fileName, "w") file.write(self.textBox.get(1.0, tk.END)) file.close() # Setup what happens when command menu is clicked def commandClick(self, item): startIndex = self.textBox.index(tk.INSERT) tabCheckString = self.textBox.get(str(float(startIndex) // 1), startIndex) tabCount = tabCheckString.count("\t") self.textBox.insert(tk.INSERT, item) if item != "else": self.textBox.insert(tk.INSERT, "()") self.textBox.insert(tk.INSERT, ":\n\t") for i in range(tabCount): self.textBox.insert(tk.INSERT, "\t") if item != "else": self.textBox.mark_set(tk.INSERT, startIndex.split(".")[0] + "." + str(int(startIndex.split(".")[1])+len(item)+1)) return "break" # Setup what happens when expression menu is clicked def expressionClick(self, item): self.textBox.insert(tk.INSERT, item) self.helpText.set(ld.helpInfo + ld.expressionExplanationList[self.expressionList.index(item)]) return "break" # Setup what happens when equation menu is clicked def equationClick(self, item): self.textBox.insert(tk.INSERT, item) self.helpText.set(ld.helpInfo + ld.equationExplanationList[self.equationList.index(item)]) return "break" # Setup what happens when regular function menu is clicked def functionClick(self, item): startIndex = self.textBox.index(tk.INSERT) self.textBox.insert(tk.INSERT, item+"\n") self.helpText.set(ld.helpInfo + ld.functionExplanationList[self.functionList.index(item)]) self.recolorize() self.textBox.mark_set(tk.INSERT, startIndex.split(".")[0] + "." + str(int(startIndex.split(".")[1])+len(item)-1)) return "break" # Setup what happens when external function menu is clicked def function2Click(self, item): self.textBox.insert("1.0", "import " + item.split(".")[0]+"\n") self.textBox.insert(tk.INSERT, item) self.helpText.set(ld.helpInfo + ld.functionExplanationList2[self.functionList2.index(item)]) return "break" # Sets up advanced mode def setAdvancedMode(self): self.commandMenu.delete(0, 'end') self.expressionMenu.delete(0, 'end') self.advancedMode ^= 1 if self.advancedMode: for i in self.advancedCommandList: self.commandList.append(i) for i in self.advancedExpressionList: self.expressionList.append(i) else: for i in self.advancedCommandList: while i in self.commandList: self.commandList.remove(i) for i in self.advancedExpressionList: while i in self.expressionList: self.expressionList.remove(i) for i in self.commandList: self.commandMenu.add_command(label=i) for i in self.expressionList: self.expressionMenu.add_command(label=i) # Setup what happens when move menu is clicked: prints selected part and direction to textbox as one would use it in code. def moveClick(self, library, device, part, movement): if "import " + library not in self.textBox.get("1.0", tk.END): self.textBox.insert("1.0", "import " + library + "\n") self.textBox.insert(tk.INSERT, type(device).__name__.lower() + " = " + library + "." + type(device).__name__ + "()\n") self.textBox.insert(tk.INSERT, type(device).__name__.lower() + "." + part + "." + movement + "()\n") self.recolorize() # Syntax highlighting code. Changes pieces of code to bold/italic according to the style sheet. def create_tags(self): bold_font = font.Font(self.textBox, self.textBox.cget("font")) bold_font.configure(weight=font.BOLD) italic_font = font.Font(self.textBox, self.textBox.cget("font")) italic_font.configure(slant=font.ITALIC) bold_italic_font = font.Font(self.textBox, self.textBox.cget("font")) bold_italic_font.configure(weight=font.BOLD, slant=font.ITALIC) style = get_style_by_name('colorful') for ttype, ndef in style: tag_font = None if ndef['bold'] and ndef['italic']: tag_font = bold_italic_font elif ndef['bold']: tag_font = bold_font elif ndef['italic']: tag_font = italic_font if ndef['color']: foreground = "#%s" % ndef['color'] else: foreground = None self.textBox.tag_configure(str(ttype), foreground=foreground, font=tag_font) # Syntax highlighting code. Changes color of pieces of code specified in style sheet. def recolorize(self, _event=None): self.create_tags() code = self.textBox.get("1.0", "end-1c") tokensource = self.lexer.get_tokens(code) start_line = 1 start_index = 0 end_line = 1 end_index = 0 for ttype, value in tokensource: if "\n" in value: end_line += value.count("\n") end_index = len(value.rsplit("\n", 1)[1]) else: end_index += len(value) if value not in (" ", "\n"): index1 = "%s.%s" % (start_line, start_index) index2 = "%s.%s" % (end_line, end_index) for tagname in self.textBox.tag_names(index1): self.textBox.tag_remove(tagname, index1, index2) self.textBox.tag_add(str(ttype), index1, index2) start_line = end_line start_index = end_index
class Editor(tk.Frame): def __init__(self, root, *args, **kwargs): # Unable to understand why self is necessary as first argument tk.Frame.__init__(self, root, *args, **kwargs) self.root = root self.__filename = None self.__saved = tk.BooleanVar() self.__modified = tk.BooleanVar() self.build_editor() self.build_context_menu() root.bind_class("Text", "<Control-A>", self.select_all) root.bind_class("Text", "<Control-a>", self.select_all) self.config_tags() self.create_tags() self.set_lexer() def build_editor(self): """Builds the Editor ->ScrolledText""" self.textpad = scrolledtext.ScrolledText(self, undo=True) self.textpad.pack(expand=tk.YES, fill=tk.BOTH) self.textpad.focus_set() def cmenupopup(self, event): self.contextmenu.tk_popup(event.x_root, event.y_root) def build_context_menu(self): self.contextmenu = tk.Menu(self, tearoff=0) self.contextmenu.add_command( label="Cut", accelerator="Ctrl+X", command=lambda: self.textpad.event_generate("<<Cut>>")) self.contextmenu.add_command( label="Copy", accelerator="Ctrl+C", command=lambda: self.textpad.event_generate("<<Copy>>")) self.contextmenu.add_command( label="Paste", accelerator="Ctrl+V", command=lambda: self.textpad.event_generate("<<Paste>>")) self.contextmenu.add_separator() self.contextmenu.add_command(label="Select All", accelerator="Ctrl+A", command=self.select_all) self.textpad.bind("<Button-3>", self.cmenupopup) def set_text_content(self, newcontent): """Replace the text content with new content""" self.textpad.delete("1.0", "end") self.textpad.insert("1.0", newcontent) def get_text_content(self): """Get the text content""" return self.textpad.get("1.0", "end") def set_saved(self, state): """Set the saved state""" self.__saved.set(state) def get_saved(self): """Get the saved state""" return self.__saved.get() def set_modified(self, state): """Set the modified state""" self.__modified.set(state) def get_modified(self): """Get the modified state""" return self.__modified.get() def set_filename(self, name): """Set the filename""" self.__filename = name def get_filename(self): """Get filename""" return self.__filename def select_all(self, event=None): #Don't know why event as argument is required """Select all the text content""" self.textpad.tag_add("sel", "1.0", "end-1c") def has_content(self): """Returns True/False if text has content""" return (True if (len(get_text_content()) > 1) else False) def search_for(self, query, caseinsensitive): self.textpad.tag_remove("match", "1.0", "end") count = 0 if query: pos = "1.0" while True: pos = self.textpad.search(query, pos, nocase=caseinsensitive, stopindex=tk.END) if not pos: break lastpos = pos + '+' + str(len(query)) + 'c' self.textpad.tag_add("match", pos, lastpos) count += 1 pos = lastpos return count def replace_for(self, replace, replacewith, caseinsensitive): self.textpad.tag_remove("match", "1.0", "end") count = 0 if replace: pos = "1.0" while True: pos = self.textpad.search(replace, pos, nocase=caseinsensitive, stopindex=tk.END) if not pos: break lastpos = pos + '+' + str(len(replace)) + 'c' self.textpad.delete(pos, lastpos) self.textpad.insert(pos, replacewith) count += 1 pos = lastpos return count def config_tags(self): self.textpad.tag_config("Highlight", background="#D1D4D1") self.textpad.tag_config("match", foreground="red", background="yellow") def event_key(self, event): keycode = event.keycode char = event.char # print("\tkeycode %s - char %s" % (keycode, repr(char))) self.recolorize() def set_lexer(self): self.lexer = PythonLexer() # TODO : Guess Lexer from Filename #Function to create tags for highlight def create_tags(self): bold_font = font.Font(self.textpad, self.textpad.cget("font")) bold_font.configure(weight=font.BOLD) italic_font = font.Font(self.textpad, self.textpad.cget("font")) italic_font.configure(slant=font.ITALIC) bold_italic_font = font.Font(self.textpad, self.textpad.cget("font")) bold_italic_font.configure(weight=font.BOLD, slant=font.ITALIC) style = get_style_by_name('default') for ttype, ndef in style: # print(ttype, ndef) tag_font = None if ndef['bold'] and ndef['italic']: tag_font = bold_italic_font elif ndef['bold']: tag_font = bold_font elif ndef['italic']: tag_font = italic_font if ndef['color']: foreground = "#%s" % ndef['color'] else: foreground = None self.textpad.tag_configure(str(ttype), foreground=foreground, font=tag_font) #Function to recolorize according to tags created # Currently this function is very bad as it recolorizes the whole document from # start at every key stroke def recolorize(self): # print("recolorize") code = self.textpad.get("1.0", "end-1c") tokensource = self.lexer.get_tokens(code) start_line = 1 start_index = 0 end_line = 1 end_index = 0 for ttype, value in tokensource: if "\n" in value: end_line += value.count("\n") end_index = len(value.rsplit("\n", 1)[1]) else: end_index += len(value) if value not in (" ", "\n"): index1 = "%s.%s" % (start_line, start_index) index2 = "%s.%s" % (end_line, end_index) for tagname in self.textpad.tag_names(index1): # FIXME self.textpad.tag_remove(tagname, index1, index2) # print(ttype, repr(value), index1, index2) self.textpad.tag_add(str(ttype), index1, index2) start_line = end_line start_index = end_index #Function to recolorize def removecolors(self): for tag in self.tagdefs: self.textpad.tag_remove(tag, "1.0", "end")
class Editor(object): currentLanguageName = "Plain Text" currentStyleName = "default" windowWidth = 100 windowHeight = 25 def __init__(self, root, lexer): self.root = root self.TITLE = "Simple IO" self.file_path = None self.set_title() self.fontSize = 12 self.lexer = lexer self.bootstrap = [self.recolorize] frame = Frame(root) # Scroll Bar [X and Y] self.xscrollbar = Scrollbar(root, orient="horizontal") self.yscrollbar = Scrollbar(root, orient="vertical") # Textbox (The main text input area) self.editor = Text(frame, yscrollcommand=self.yscrollbar.set, xscrollcommand=self.xscrollbar.set, bg="#FFFFFF", fg="#000000", insertbackground="#000000") self.editor.pack(side="left", fill="both", expand=1) self.editor.config(wrap="none", undo=True, width=self.windowWidth, height=self.windowHeight, font=("Consolas", self.fontSize), tabs=('1c')) self.editor.focus() self.create_tags() # Scroll Bars packing self.xscrollbar.pack(side="bottom", fill="x") # Horizontal Scroll Bar self.xscrollbar.config(command=self.editor.xview) self.yscrollbar.pack(side="right", fill="y") # Vertial Scroll Bar self.yscrollbar.config(command=self.editor.yview) # ## Status Bar ## # self.statusText = (("Font Size: " + str(self.fontSize)) + " | " + "Langauge: " + self.currentLanguageName) self.status = Label(root, text=self.statusText, relief=tkinter.SUNKEN, anchor='w') self.status.pack(side=tkinter.BOTTOM, fill=tkinter.X) frame.pack(fill="both", expand=1) #instead of closing the window, execute a function. Call file_quit root.protocol("WM_DELETE_WINDOW", self.file_quit) #create a top level menu self.menubar = Menu(root) #Menu item: File filemenu = Menu( self.menubar, tearoff=0) # tearoff = 0 => can't be seperated from window filemenu.add_command(label="New", command=self.file_new, accelerator="Ctrl+N") filemenu.add_command(label="Open", command=self.file_open, accelerator="Ctrl+O") filemenu.add_command(label="Save", command=self.file_save, accelerator="Ctrl+S") filemenu.add_command(label="Save As", command=self.file_save_as, accelerator="Ctrl+Alt+S") filemenu.add_separator() # Adds a lines between the above elements filemenu.add_command(label="Exit", command=self.file_quit, accelerator="Ctrl+Q") self.menubar.add_cascade(label="File", menu=filemenu) # Menu item: View viewMenu = Menu(self.menubar, tearoff=0) viewMenu.add_command(label="Zoom In", command=self.zoom_In, accelerator="Ctrl+") viewMenu.add_command(label="Zoom Out", command=self.zoom_Out, accelerator="Ctrl-") self.menubar.add_cascade(label="View", menu=viewMenu) # Menu item: Color Scheme colorMenu = Menu(self.menubar, tearoff=0) colorMenu.add_command( label="Default", command=lambda: self.changeColorScheme("default")) colorMenu.add_command( label="Monokai", command=lambda: self.changeColorScheme("monokai")) self.menubar.add_cascade(label="Color Scheme", menu=colorMenu) # Menu item: Languages languageMenu = Menu(self.menubar, tearoff=0) languageMenu.add_command(label="Plain Text", command=self.languageLexerToPlain) languageMenu.add_command(label="Python", command=self.languageLexerToPython) self.menubar.add_cascade(label="Language", menu=languageMenu) # display the menu root.config(menu=self.menubar) # If user trys to quit while there is unsaved content def save_if_modified(self, event=None): print("Checking if current save is modified...") if self.editor.edit_modified(): #modified print("Current save is modified") response = messagebox.askyesnocancel( "Save?", "This document has been modified. Do you want to save changes?" ) #yes = True, no = False, cancel = None if response: #yes/save result = self.file_save() if result == "saved": #saved print("Saved") return True else: #save cancelled return None else: return response #None = cancel/abort, False = no/discard else: #not modified return True def updateStatusBar(self, event=None): self.statusText = (("Font Size: " + str(self.fontSize)) + " | " + "Langauge: " + self.currentLanguageName) self.status.config(text=self.statusText) # FILE MENU FUNCTIONS ############################################################################################## # NEW FILE def file_new(self, event=None): result = self.save_if_modified() if result != None: #None => Aborted or Save cancelled, False => Discarded, True = Saved or Not modified self.editor.delete(1.0, "end") self.editor.edit_modified(False) self.editor.edit_reset() self.file_path = None self.set_title() # OPEN FILE def file_open(self, event=None, filepath=None): result = self.save_if_modified() if result != None: #None => Aborted or Save cancelled, False => Discarded, True = Saved or Not modified if filepath == None: filepath = filedialog.askopenfilename() if filepath != None and filepath != '': with open(filepath, encoding="utf-8") as f: fileContents = f.read() # Get all the text from file. # Set current text to file contents self.editor.delete(1.0, "end") self.editor.insert(1.0, fileContents) self.editor.edit_modified(False) self.file_path = filepath # SAVE FILE def file_save(self, event=None): if self.file_path == None: result = self.file_save_as() else: result = self.file_save_as(filepath=self.file_path) return result # SAVE AS def file_save_as(self, event=None, filepath=None): if filepath == None: filepath = tkinter.filedialog.asksaveasfilename( filetypes=(('Text files', '*.txt'), ('Python files', '*.py *.pyw'), ('All files', '*.*'))) #defaultextension='.txt' try: with open(filepath, 'wb') as f: text = self.editor.get(1.0, "end-1c") f.write(bytes(text, 'UTF-8')) self.editor.edit_modified(False) self.file_path = filepath self.set_title() return "saved" except FileNotFoundError: print('FileNotFoundError') return "cancelled" # QUIT def file_quit(self, event=None): result = self.save_if_modified() if result != None: #None => Aborted or Save cancelled, False => Discarded, True = Saved or Not modified print("Exiting with code: 0") self.root.destroy() #sys.exit(0) # Show the file name on the top of the window def set_title(self, event=None): if self.file_path != None: title = os.path.basename(self.file_path) else: title = "Untitled" self.root.title(title + " - " + self.TITLE) def undo(self, event=None): self.editor.edit_undo() def redo(self, event=None): self.editor.edit_redo() # VIEW MENU FUNCTIONS ############################################################################################### def zoom_Out(self, event=None): print("Zooming Out") if self.fontSize > 9: self.fontSize -= 1 self.editor.config(font=("Helvetica", self.fontSize)) self.updateStatusBar() def zoom_In(self, event=None): print("Zooming In") if self.fontSize < 50: self.fontSize += 1 self.editor.config(font=("Helvetica", self.fontSize)) self.updateStatusBar() # LANGUAGE MENU FUNCTIONS ############################################################################################### def languageLexerToPlain(self, event=None): print("Setting language to: Plain Text") self.lexer = TextLexer() self.currentLanguageName = "Plain Text" self.create_tags() self.recolorize() self.updateStatusBar() def languageLexerToPython(self, event=None): print("Setting language to: Python") self.lexer = PythonLexer() self.currentLanguageName = "Python" self.create_tags() self.recolorize() self.updateStatusBar() # COLOR SCHEME MENU FUNCTIONS ############################################################################################### def changeColorScheme(self, styleParam): self.currentStyleName = styleParam print("Changing style to: " + styleParam) self.create_tags() self.recolorize() # Changing the background color if styleParam == "default": self.editor.config(bg="#FFFFFF", fg="#000000", insertbackground="#000000") elif styleParam == "monokai": self.editor.config(bg="#272822", fg="#FFFFFF", insertbackground="#FFFFFF") # EVENTS ############################################################################################### def event_KeyPressed(self, event=None): self.recolorize() def create_tags(self): """ this method creates the tags associated with each distinct style element of the source code 'dressing' """ bold_font = font.Font(self.editor, self.editor.cget("font")) bold_font.configure(weight=font.BOLD) italic_font = font.Font(self.editor, self.editor.cget("font")) italic_font.configure(slant=font.ITALIC) bold_italic_font = font.Font(self.editor, self.editor.cget("font")) bold_italic_font.configure(weight=font.BOLD, slant=font.ITALIC) style = get_style_by_name(self.currentStyleName) for ttype, ndef in style: tag_font = None if ndef['bold'] and ndef['italic']: tag_font = bold_italic_font elif ndef['bold']: tag_font = bold_font elif ndef['italic']: tag_font = italic_font if ndef['color']: foreground = "#%s" % ndef['color'] else: foreground = None self.editor.tag_configure(str(ttype), foreground=foreground, font=tag_font) # This methd colors the text by using the tokens in the lexer def recolorize(self): code = self.editor.get("1.0", "end-1c") tokensource = self.lexer.get_tokens(code) start_line = 1 start_index = 0 end_line = 1 end_index = 0 for ttype, value in tokensource: if "\n" in value: end_line += value.count("\n") end_index = len(value.rsplit("\n", 1)[1]) else: end_index += len(value) if value not in (" ", "\n"): index1 = "%s.%s" % (start_line, start_index) index2 = "%s.%s" % (end_line, end_index) for tagname in self.editor.tag_names(index1): # FIXME self.editor.tag_remove(tagname, index1, index2) self.editor.tag_add(str(ttype), index1, index2) start_line = end_line start_index = end_index def main(self, event=None): # Key bindings self.editor.bind("<Control-o>", self.file_open) # Open File self.editor.bind("<Control-O>", self.file_open) # ^ self.editor.bind("<Control-S>", self.file_save) # Save File self.editor.bind("<Control-s>", self.file_save) # ^ self.editor.bind("<Control-q>", self.file_quit) # Quit Application self.editor.bind("<Control-Q>", self.file_quit) # ^ self.editor.bind("<Control-y>", self.redo) # Redo Action self.editor.bind("<Control-Y>", self.redo) # ^ self.editor.bind("<Control-Z>", self.undo) # Undo Action self.editor.bind("<Control-z>", self.undo) # ^ self.editor.bind("<Control-minus>", self.zoom_Out) self.editor.bind("<Control-plus>", self.zoom_In) self.editor.bind("<Key>", self.event_KeyPressed)
class Tktext(object): def __init__(self, master, txt): self.lexer = PythonLexer() self.txt = txt # any key colorize master.bind('<Key>', self.event_key) # paste master.bind('<Control-v>', self.event_paste) # copy master.bind('<Control-c>', self.event_key) master.update() self.create_tags() # paste method def paste(self, text): if text: self.txt.insert(INSERT, text) self.txt.tag_remove(SEL, '1.0', END) self.txt.see(INSERT) self.recolorize() # any key colorize def event_key(self, event): if event.keycode == 17: return 'break' self.recolorize() def event_paste(self, event=None): self.recolorize() def create_tags(self): bold_font = font.Font(self.txt, self.txt.cget("font")) bold_font.configure(weight=font.BOLD) italic_font = font.Font(self.txt, self.txt.cget("font")) italic_font.configure(slant=font.ITALIC) bold_italic_font = font.Font(self.txt, self.txt.cget("font")) bold_italic_font.configure(weight=font.BOLD, slant=font.ITALIC) style = get_style_by_name('sas') for ttype, ndef in style: # print(ttype, ndef) tag_font = None if ndef['bold'] and ndef['italic']: tag_font = bold_italic_font elif ndef['bold']: tag_font = bold_font elif ndef['italic']: tag_font = italic_font if ndef['color']: foreground = "#%s" % ndef['color'] else: foreground = None self.txt.tag_configure(str(ttype), foreground=foreground, font=tag_font) def recolorize(self): code = self.txt.get("1.0", "end-1c") tokensource = self.lexer.get_tokens(code) start_line = 1 start_index = 0 end_line = 1 end_index = 0 for ttype, value in tokensource: if "\n" in value: end_line += value.count("\n") end_index = len(value.rsplit("\n", 1)[1]) else: end_index += len(value) if value not in (" ", "\n"): index1 = "%s.%s" % (start_line, start_index) index2 = "%s.%s" % (end_line, end_index) for tagname in self.txt.tag_names(index1): # FIXME self.txt.tag_remove(tagname, index1, index2) # print(ttype, repr(value), index1, index2) self.txt.tag_add(str(ttype), index1, index2) start_line = end_line start_index = end_index def removecolors(self): for tag in self.tagdefs: self.txt.tag_remove(tag, "1.0", "end")
class Window: def __init__(self, master): self.master = master self.master.title('프로그래밍 언어 만들기') self.master.config(bg='#fff') self.master.geometry('1024x768') self.lexer = PythonLexer(stripnl=False) self.AddWidgets() self.create_tags() self.current_level = db.get('level', 0) self.set_level(self.current_level) def set_level(self, level): try: level = int(level) except: level = 0 if level < 0: level = 0 if level >= len(quizs): level = len(quizs) - 1 #if not ? and level > self.set_quiz(level) def set_quiz(self, level): self.current_level = level q = quizs[level] self.title_var.set("언어 만들기 - " + q.title) self.text.delete("1.0", tkinter.END) self.text.insert(tkinter.END, db.setdefault('code' + str(level), '')) self.recolorize() if db['pass' + str(self.current_level)]: self.tutstatus.config(text='통과', fg='#adff2f') else: self.tutstatus.config(text='') self.desc.config(state=tkinter.NORMAL) self.desc.delete("1.0", tkinter.END) self.desc.insert( tkinter.END, '목표\n' + q.desc.replace('<code>', '').replace('</code>', '')) #''.join(''.join(escape(x) for x in y.split('</code>')) for y in q.desc.split('<code>'))) self.desc.tag_add("BigTitle", "1.0", "2.0") self.desc.config(state=tkinter.DISABLED) self.examples.config(state=tkinter.NORMAL) self.examples.delete("1.0", tkinter.END) self.examples.insert(tkinter.END, '예제\n') self.examples.tag_add("BigTitle", "1.0", "2.0") if q.examples: self.examples.insert( tkinter.END, '\n ----- \n'.join( "입력 " + str(idx + 1) + "\n" + x.generate_output() for idx, x in enumerate(q.examples))) else: self.examples.insert(tkinter.END, '(예제 없음)') self.examples.config(state=tkinter.DISABLED) def select_all(self, *args): self.text.tag_add("sel", "1.0", "end") def AddWidgets(self): global console self.master.configure(bg='white') self.master.grid_columnconfigure(0, weight=1, uniform=2) self.master.grid_columnconfigure(1, weight=1, uniform=2) self.master.grid_rowconfigure(0, minsize=50) self.master.grid_rowconfigure(1, weight=1, uniform=1) self.master.grid_rowconfigure(2, weight=1, uniform=1) self.master.grid_rowconfigure(3, weight=1, uniform=1) self.master.grid_rowconfigure(4, weight=1, uniform=1) console = scrolledtext.ScrolledText(master=self.master, ) self.text = scrolledtext.ScrolledText( master=self.master, bg='#272822', undo=True, exportselection=True, cursor='xterm', selectbackground='#ddd', insertbackground='#fff', insertwidth='3', ) #, **self.uiopts) self.text.grid(column=1, row=1, rowspan=3, sticky=('nsew')) console.grid(column=1, row=4, rowspan=1, sticky=('nsew')) for command, key in ('Copy', 'c'), ('Paste', 'v'), ('Cut', 'x'): self.text.event_add('<<' + command + '>>', '<Command-' + key + '>') self.text.event_add('<<' + command + '>>', '<Command-' + key.upper() + '>') self.text.event_add('<<' + command + '>>', '<Control-' + key + '>') self.text.event_add('<<' + command + '>>', '<Control-' + key.upper() + '>') self.text.bind("<Command-a>", self.select_all) self.text.bind("<Command-A>", self.select_all) self.text.bind("<Control-a>", self.select_all) self.text.bind("<Control-A>", self.select_all) self.desc = scrolledtext.ScrolledText( self.master #,highlightbackground="#7c7cfa", highlightcolor="#7c7cfa", highlightthickness=1, spacing1 = 5, spacing3 = 5, bg='#ddd' ) self.desc.grid(column=0, row=1, rowspan=2, sticky=('nsew'), padx=5, pady=5) self.desc.bind("<1>", lambda event: self.desc.focus_set()) self.examples = scrolledtext.ScrolledText( self.master, #highlightbackground="#7c7cfa", highlightcolor="#7c7cfa", highlightthickness=1, spacing1 = 5, spacing3 = 5, bg='#eee' ) self.examples.grid(column=0, row=3, rowspan=2, sticky=('nsew'), padx=5, pady=5) self.examples.bind("<1>", lambda event: self.examples.focus_set()) self.navbar = tkinter.Frame( self.master, height=50, bg='#7c7cfa', #highlightbackground="red", highlightcolor="red", highlightthickness=1 ) self.title_var = tkinter.StringVar() self.title1 = tkinter.Label(self.navbar, textvariable=self.title_var, bg='#7c7cfa', fg='white') self.title1.configure(font=('monaco', 27)) self.title_var.set("언어 만들기 - ") self.title1.pack(side=tkinter.LEFT, padx=5) right_btn = tkinter.Button(self.navbar, text='→', highlightbackground='#7c7cfa', highlightcolor='#7c7cfa') right_btn.pack(side=tkinter.RIGHT) right_btn.configure( command=lambda: self.set_level(self.current_level + 1)) submit_btn = tkinter.Button(self.navbar, text="제출", highlightbackground='#7c7cfa', highlightcolor='#7c7cfa') submit_btn.pack(side=tkinter.RIGHT) submit_btn.configure(command=lambda: self.do_submit()) test_btn = tkinter.Button(self.navbar, text="테스트", highlightbackground='#7c7cfa', highlightcolor='#7c7cfa') test_btn.pack(side=tkinter.RIGHT) test_btn.configure(command=lambda: self.do_test()) left_btn = tkinter.Button(self.navbar, text='←', highlightbackground='#7c7cfa', highlightcolor='#7c7cfa') left_btn.pack(side=tkinter.RIGHT) left_btn.configure( command=lambda: self.set_level(self.current_level - 1)) self.tutstatus = tkinter.Label(self.navbar, text='', fg='#adff2f', bg='#7c7cfa') self.tutstatus.pack(side=tkinter.RIGHT) self.tutstatus.configure(font=('monaco', 27)) self.navbar.grid(column=0, row=0, columnspan=2, sticky=tkinter.W + tkinter.E + tkinter.N + tkinter.S) #self.examples.insert(tkinter.INSERT,'abc\n'*100) #self.desc.insert(tkinter.INSERT,'desc') #self.text.insert(tkinter.INSERT, 'code') # NORMAL self.examples.configure(font=('monaco', 14)) self.desc.configure(font=('monaco', 14)) self.text.configure(font=('monaco', 16)) self.desc.config(state=tkinter.DISABLED) self.examples.config(state=tkinter.DISABLED) self.text.bind("<BackSpace>", self.do_backspace) self.master.bind('<Key>', self.event_key) self.text.bind('<Return>', self.autoindent) self.text.bind('<Tab>', self.tab2spaces4) self.recolorize() def do_backspace(self, event): lineindex, columnindex = self.text.index("insert").split(".") linetext = self.text.get(lineindex + ".0", self.text.index('insert')) if int(columnindex) > 0 and all(x == ' ' for x in linetext): self.text.delete("insert-%d chars" % min(int(columnindex), 4), "insert") return 'break' def autoindent(self, event): indentation = "" lineindex = self.text.index("insert").split(".")[0] linetext = self.text.get(lineindex + ".0", lineindex + ".end") for character in linetext: if character in [" ", "\t"]: indentation += character else: break self.text.insert(self.text.index("insert"), "\n" + indentation) return "break" def tab2spaces4(self, event): #idx = self.text.index("insert") self.text.insert(self.text.index("insert"), " ") return "break" #self.text.insert(tkinter.INSERT, text) #self.text.tag_remove(tkinter.SEL, '1.0', tkinter.END) #self.text.see(tkinter.INSERT) def event_key(self, event): keycode = event.keycode char = event.char self.recolorize() self.master.update() def event_mouse(self, event): pass def close(self): self.master.destroy() def recolorize(self): code = self.text.get("1.0", "end-1c") tokensource = self.lexer.get_tokens(code) start_line = 1 start_index = 0 end_line = 1 end_index = 0 for ttype, value in tokensource: if "\n" in value: end_line += value.count("\n") end_index = len(value.rsplit("\n", 1)[1]) else: end_index += len(value) if value not in (" ", "\n"): index1 = "%s.%s" % (start_line, start_index) index2 = "%s.%s" % (end_line, end_index) for tagname in self.text.tag_names(index1): if tagname == 'sel': continue self.text.tag_remove(tagname, index1, index2) self.text.tag_add(str(ttype), index1, index2) start_line = end_line start_index = end_index def do_test(self): code = self.text.get("1.0", "end") q = quizs[self.current_level] db['code' + str(self.current_level)] = code.rstrip('\n') save() examples = q.examples if not examples: examples = [Example("")] gui.run(code, self.master, str(examples[0])) def show_pass(self): messagebox.showinfo("성공", "통과하였습니다! 다음 레벨로 진행합니다.") self.set_level(self.current_level + 1) def show_fail(self, hint): messagebox.showerror("실패", "실패하였습니다. 힌트를 참고하여 다시 작성해 보세요.\n\n" + hint) def do_submit(self): code = self.text.get("1.0", "end") q = quizs[self.current_level] db['code%s' % self.current_level] = code.rstrip('\n') pass_this_time = True examples = q.examples if not examples: examples = [Example("")] for idx, e in enumerate(examples): has_exception = False goal_has_exception = False res = 1 goal = 2 my_exc = None goal_exc = None example_input = str(e) try: res = verify.run(code, example_input) except Exception as exc: has_exception = True my_exc = exc try: goal = verify.run(q.goal.goal, example_input) except Exception as exc: goal_has_exception = True goal_exc = exc raise print(goal, res, goal_has_exception, has_exception, my_exc, goal_exc) if goal == res or goal_has_exception and has_exception and type( my_exc) is type(goal_exc): print('예제 %s 통과' % (idx + 1)) else: print('예제 %s 실패' % (idx + 1)) pass_this_time = False level = db.setdefault('level', 0) if pass_this_time: if level <= self.current_level: db['level'] = self.current_level + 1 print('통과했습니다!') self.tutstatus.config(text='통과', fg='#adff2f') if not db.setdefault('pass' + str(self.current_level), 0): db['pass' + str(self.current_level)] = 1 try: res = urlopen('http://ipkn.me:4949/pass/' + str(self.current_level)).read() except: pass self.show_pass() else: print('통과하지 못했습니다.') if not db['pass' + str(self.current_level)]: self.tutstatus.config(text='실패', fg='#ff2f1f') try: res = urlopen('http://ipkn.me:4949/fail/' + str(self.current_level)).read() except: pass k = 'fail' + str(self.current_level) db[k] = db.get(k, 0) + 1 self.show_fail(q.hint.generate(db[k])) save() def create_tags(self): bold_font = font.Font(self.text, self.text.cget("font")) bold_font.configure(weight=font.BOLD) italic_font = font.Font(self.text, self.text.cget("font")) italic_font.configure(slant=font.ITALIC) bold_italic_font = font.Font(self.text, self.text.cget("font")) bold_italic_font.configure(weight=font.BOLD, slant=font.ITALIC) style = get_style_by_name('monokai') self.desc.tag_configure('BigTitle', font=('monaca', 23)) self.examples.tag_configure('BigTitle', font=('monaca', 23)) for ttype, ndef in style: tag_font = None if ndef['bold'] and ndef['italic']: tag_font = bold_italic_font elif ndef['bold']: tag_font = bold_font elif ndef['italic']: tag_font = italic_font if ndef['color']: foreground = "#%s" % ndef['color'] else: foreground = None self.text.tag_configure(str(ttype), foreground=foreground, font=tag_font)