class NamedWindow: """ This creates a window for the Tkui which you can then write to programmatically. This allows modules to spin off new named windows and write to them. """ def __init__(self, windowname, master, partk): """ Initializes the window @param windowname: the name of the new window @type windowname: string @param master: the main tk window @type master: toplevel """ self._parent = master self._tk = Toplevel(partk) self._windowname = windowname # map of session -> (bold, foreground, background) self._currcolors = {} # ses -> string self._unfinishedcolor = {} self._do_i_echo = 1 self._tk.geometry("500x300") self._tk.title("Lyntin -- " + self._windowname) self._tk.protocol("WM_DELETE_WINDOW", self.close) if os.name == "posix": fontname = "Courier" else: fontname = "Fixedsys" fnt = tkFont.Font(family=fontname, size=12) self._txt = ScrolledText(self._tk, fg="white", bg="black", font=fnt, height=20) self._txt.pack(side=TOP, fill=BOTH, expand=1) # handles improper keypresses self._txt.bind("<KeyPress>", self._ignoreThis) # initialize color tags self._initColorTags() def convertColor(self, name): """ Tk has this really weird color palatte. So I switched to using color names in most cases and rgb values in cases where I couldn't find a good color name. This method allows me to specify either an rgb or a color name and it converts the color names to rgb. @param name: either an rgb value or a name @type name: string @returns: the rgb color value @rtype: string """ if name[0] == "#": return name rgb = self._tk._getints(self._tk.tk.call("winfo", "rgb", self._txt, name)) rgb = "#%02x%02x%02x" % (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) print name, "converted to: ", rgb return rgb def _initColorTags(self): """ Sets up Tk tags for the text widget (fg/bg).""" for ck in fg_color_codes.keys(): color = self.convertColor(fg_color_codes[ck]) self._txt.tag_config(ck, foreground=color) for ck in bg_color_codes.keys(): self._txt.tag_config(ck, background=bg_color_codes[ck]) self._txt.tag_config("u", underline=1) def _ignoreThis(self, tkevent): """ This catches keypresses to this window. """ return "break" def close(self): """ Closes and destroys references to this window. """ self._parent.removeWindow(self._windowname) self._tk.destroy() def _yadjust(self): """Handles y scrolling after text insertion.""" self._txt.yview("moveto", "1") # if os.name != 'posix': self._txt.yview("scroll", "20", "units") def _clipText(self): """ Scrolls the text buffer up so that the new text written at the bottom of the text buffer can be seen. """ temp = self._txt.index("end") ind = temp.find(".") temp = temp[:ind] if temp.isdigit() and int(temp) > 800: self._txt.delete("1.0", "100.end") def write(self, msg): """ This writes text to the text buffer for viewing by the user. This is overridden from the 'base.BaseUI'. """ if type(msg) == types.TupleType: msg = msg[0] if type(msg) == types.StringType: msg = message.Message(msg, message.LTDATA) line = msg.data ses = msg.session if line == "": return color, leftover = buffer_write(msg, self._txt, self._currcolors, self._unfinishedcolor) if msg.type == message.MUDDATA: self._unfinishedcolor[ses] = leftover self._currcolors[ses] = color self._clipText() self._yadjust()
class MyThing (Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.textBox = ScrolledText(self, height=30, width=85) self.textBox.pack() self.addText() def leftMouseClick(self,event): print "got a click" mouseIndex = "@%d,%d" % (event.x, event.y) current = self.textBox.index(mouseIndex) row = current.split(".")[0] + ".0" rowFl = float(row)+1.0 row = str(rowFl) target = self.rowIndex[row] target = float(target)+12.0 target = str(target) print "moving to target",target self.textBox.see(target) def rightMouseClick(self,event): self.textBox.see("0.0") def addText(self): f=open("help.txt",'r') lines = f.readlines() f.close() flag = 1 i=0 sectionOrder = {} sectionHeads = {} outlines = {} targetId = 0 defaultTarget = "" tocEntries = [] lineId=0 for line in lines: if line[0:1] =="#": tocEntries.append(line) if flag: top = lineId flag = 0 lineId+=1 self.tocEntries = tocEntries # header text header = lines[0:top] for line in header: hid=self.textBox.insert(END,line,"header") self.textBox.tag_config("header",foreground=FGCOLOR) self.textBox.tag_config("header",background=BGCOLOR) self.textBox.insert(END,"Table of Contents\n","toc") self.textBox.tag_config("toc",foreground="red") self.textBox.tag_config("toc",background=BGCOLOR) self.textBox.insert(END, "(Left-click entry to navigate, right-click to return)\n\n","directions") self.textBox.tag_config("directions",background=BGCOLOR) self.textBox.tag_config("directions",foreground="purple") sectionDict = {} rowIndex = {} for tocEntry in tocEntries: if tocEntry[0:2] == "##": line = "\t"+tocEntry[2:] else: line = tocEntry[1:] rowPosition = self.textBox.index(END).split(".")[0] + ".0" rowIndex[rowPosition] = "0.0" sectionDict[tocEntry] = rowPosition self.textBox.insert(END,line,"tocEntry") self.textBox.tag_bind("tocEntry",'<ButtonRelease-1>', self.leftMouseClick) self.textBox.tag_config("tocEntry",background=BGCOLOR) self.textBox.tag_config("tocEntry",foreground=hfg) for i in range(50): self.textBox.insert(END,"\n","regular") lines = lines[top:] for line in lines: if sectionDict.has_key(line): print "section id",line position = self.textBox.index(END) tocPosition = sectionDict[line] rowIndex[tocPosition] = position line = line.replace("#","") self.textBox.insert(END,line,"regular") self.rowIndex = rowIndex self.sectionDict = sectionDict self.textBox.see("0.0") self.textBox.bind_all('<ButtonRelease-3>', self.rightMouseClick) self.textBox.tag_config("regular",background=BGCOLOR) self.textBox.tag_config("regular",foreground="black")
class Tkui(base.BaseUI): """ This is a ui class which handles the complete Tk user interface. """ def __init__(self): """ Initializes.""" base.BaseUI.__init__(self) # internal ui queue self._event_queue = Queue.Queue() # map of session -> (bold, foreground, background) self._currcolors = {} # ses -> string self._unfinishedcolor = {} self._viewhistory = 0 self._do_i_echo = 1 # holds a map of window names -> window references self._windows = {} # instantiate all the widgets self._tk = Tk() self._tk.geometry("800x600") self.settitle() if os.name == "posix": fnt = tkFont.Font(family="Courier", size=12) else: fnt = tkFont.Font(family="Fixedsys", size=12) self._entry = CommandEntry( self._tk, self, fg="white", bg="black", insertbackground="yellow", font=fnt, insertwidth="2" ) self._entry.pack(side="bottom", fill="both") self._topframe = Frame(self._tk) self._topframe.pack(side="top", fill="both", expand=1) self._txt = ScrolledText(self._topframe, fg="white", bg="black", font=fnt, height=20) self._txt.pack(side="bottom", fill="both", expand=1) self._txt.bind("<KeyPress>", self._ignoreThis) self._txtbuffer = ScrolledText(self._topframe, fg="white", bg="black", font=fnt, height=20) self._txtbuffer.bind("<KeyPress-Escape>", self.escape) self._txtbuffer.bind("<KeyPress>", self._ignoreThis) self._entry.focus_set() self._initColorTags() self.dequeue() exported.hook_register("config_change_hook", self.configChangeHandler) exported.hook_register("to_user_hook", self.write) # FIXME - fix this explanation. this is just terrible. tc = config.BoolConfig( "saveinputhighlight", 0, 1, "Allows you to change the behavior of the command entry. When " "saveinputhighlight is off, we discard whatever is on the entry " "line. When it is on, we will retain the contents allowing you " "to press the enter key to do whatever you typed again.", ) exported.add_config("saveinputhighlight", tc) self._quit = 0 def runui(self): global HELP_TEXT exported.add_help("tkui", HELP_TEXT) exported.write_message('For tk help type "#help tkui".') exported.add_command("colorcheck", colorcheck_cmd) # run the tk mainloop here self._tk.mainloop() def wantMainThread(self): # The tkui needs the main thread of execution so we return # a 1 here. return 1 def quit(self): if not self._quit: self._quit = 1 self._topframe.quit() def dequeue(self): qsize = self._event_queue.qsize() if qsize > 10: qsize = 10 for i in range(qsize): ev = self._event_queue.get_nowait() ev.execute(self) self._tk.after(25, self.dequeue) def settitle(self, title=""): """ Sets the title bar to the Lyntin title plus the given string. @param title: the title to set @type title: string """ if title: title = constants.LYNTINTITLE + title else: title = constants.LYNTINTITLE self._event_queue.put(_TitleEvent(self._tk, title)) def removeWindow(self, windowname): """ This removes a NamedWindow from our list of NamedWindows. @param windowname: the name of the window to write to @type windowname: string """ if self._windows.has_key(windowname): del self._windows[windowname] def writeWindow(self, windowname, message): """ This writes to the window named "windowname". If the window does not exist, we spin one off. It handles ansi text and messages just like writing to the main window. @param windowname: the name of the window to write to @type windowname: string @param message: the message to write to the window @type message: string or Message instance """ self._event_queue.put(_WriteWindowEvent(windowname, message)) def writeWindow_internal(self, windowname, message): if not self._windows.has_key(windowname): self._windows[windowname] = NamedWindow(windowname, self, self._tk) self._windows[windowname].write(message) def _ignoreThis(self, tkevent): """ This catches keypresses from the history buffer.""" # kludge so that ctrl-c doesn't get caught allowing windows # users to copy the buffer.... if tkevent.keycode == 17 or tkevent.keycode == 67: return self._entry.focus() if tkevent.char: # we do this little song and dance so as to pass events # we don't want to deal with to the entry widget essentially # by creating a new event and tossing it in the event list. # it only sort of works--but it's the best code we've got # so far. args = ("event", "generate", self._entry, "<KeyPress>") args = args + ("-rootx", tkevent.x_root) args = args + ("-rooty", tkevent.y_root) args = args + ("-keycode", tkevent.keycode) args = args + ("-keysym", tkevent.keysym) self._tk.tk.call(args) return "break" def pageUp(self): """ Handles prior (Page-Up) events.""" if self._viewhistory == 0: self._txtbuffer.pack(side="top", fill="both", expand=1) self._viewhistory = 1 self._txtbuffer.delete("1.0", "end") lotofstuff = self._txt.get("1.0", "end") self._txtbuffer.insert("end", lotofstuff) for t in self._txt.tag_names(): taux = None tst = 0 for e in self._txt.tag_ranges(t): if tst == 0: taux = e tst = 1 else: tst = 0 self._txtbuffer.tag_add(t, str(taux), str(e)) self._txtbuffer.yview("moveto", "1") if os.name != "posix": self._txtbuffer.yview("scroll", "20", "units") self._tk.update_idletasks() self._txt.yview("moveto", "1.0") if os.name != "posix": self._txt.yview("scroll", "220", "units") else: # yscroll up stuff self._txtbuffer.yview("scroll", "-15", "units") def pageDown(self): """ Handles next (Page-Down) events.""" if self._viewhistory == 1: # yscroll down stuff self._txtbuffer.yview("scroll", "15", "units") def escape(self, tkevent): """ Handles escape (Escape) events.""" if self._viewhistory == 1: self._txtbuffer.forget() self._viewhistory = 0 else: self._entry.clearInput() def configChangeHandler(self, args): """ This handles config changes including mudecho. """ name = args["name"] newvalue = args["newvalue"] if name == "mudecho": if newvalue == 1: # echo on self._do_i_echo = 1 self._entry.configure(show="") else: # echo off self._do_i_echo = 0 self._entry.configure(show="*") def _yadjust(self): """Handles y scrolling after text insertion.""" self._txt.yview("moveto", "1") # if os.name != 'posix': self._txt.yview("scroll", "20", "units") def _clipText(self): """ Scrolls the text buffer up so that the new text written at the bottom of the text buffer can be seen. """ temp = self._txt.index("end") ind = temp.find(".") temp = temp[:ind] if temp.isdigit() and int(temp) > 800: self._txt.delete("1.0", "100.end") def write(self, args): """ This writes text to the text buffer for viewing by the user. This is overridden from the 'base.BaseUI'. """ self._event_queue.put(_OutputEvent(args)) def write_internal(self, args): mess = args["message"] if type(mess) == types.StringType: mess = message.Message(mess, message.LTDATA) line = mess.data ses = mess.session if line == "" or self.showTextForSession(ses) == 0: return color, leftover = buffer_write(mess, self._txt, self._currcolors, self._unfinishedcolor) if mess.type == message.MUDDATA: self._unfinishedcolor[ses] = leftover self._currcolors[ses] = color self._clipText() self._yadjust() def convertColor(self, name): """ Tk has this really weird color palatte. So I switched to using color names in most cases and rgb values in cases where I couldn't find a good color name. This method allows me to specify either an rgb or a color name and it converts the color names to rgb. @param name: either an rgb value or a name @type name: string @returns: the rgb color value @rtype: string """ if name.startswith("#"): return name rgb = self._tk._getints(self._tk.tk.call("winfo", "rgb", self._txt, name)) rgb = "#%02x%02x%02x" % (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) print name, "converted to: ", rgb return rgb def _initColorTags(self): """ Sets up Tk tags for the text widget (fg/bg/u).""" for ck in fg_color_codes.keys(): color = self.convertColor(fg_color_codes[ck]) self._txt.tag_config(ck, foreground=color) self._txtbuffer.tag_config(ck, foreground=color) for ck in bg_color_codes.keys(): self._txt.tag_config(ck, background=bg_color_codes[ck]) self._txtbuffer.tag_config(ck, background=bg_color_codes[ck]) self._txt.tag_config("u", underline=1) self._txtbuffer.tag_config("u", underline=1) def colorCheck(self): """ Goes through and displays all the combinations of fg and bg with the text string involved. Purely for debugging purposes. """ fgkeys = ["30", "31", "32", "33", "34", "35", "36", "37"] bgkeys = ["40", "41", "42", "43", "44", "45", "46", "47"] self._txt.insert("end", "color check:\n") for bg in bgkeys: for fg in fgkeys: self._txt.insert("end", str(fg), (fg, bg)) self._txt.insert("end", str("b" + fg), ("b" + fg, bg)) self._txt.insert("end", "\n") for fg in fgkeys: self._txt.insert("end", str(fg), (fg, "b" + bg)) self._txt.insert("end", str("b" + fg), ("b" + fg, "b" + bg)) self._txt.insert("end", "\n") self._txt.insert("end", "\n") self._txt.insert("end", "\n")
class Tkui(base.BaseUI): """ This is a ui class which handles the complete Tk user interface. """ def __init__(self): """ Initializes.""" base.BaseUI.__init__(self) # internal ui queue self._event_queue = Queue.Queue() # map of session -> (bold, foreground, background) self._currcolors = {} # ses -> string self._unfinishedcolor = {} self._viewhistory = 0 self._do_i_echo = 1 # holds a map of window names -> window references self._windows = {} # instantiate all the widgets self._tk = Tk() self._tk.geometry("800x600") self.settitle() if os.name == 'posix': fnt = tkFont.Font(family="Courier", size=12) else: fnt = tkFont.Font(family="Fixedsys", size=12) self._entry = CommandEntry(self._tk, self, fg='white', bg='black', insertbackground='yellow', font=fnt, insertwidth='2') self._entry.pack(side='bottom', fill='both') self._topframe = Frame(self._tk) self._topframe.pack(side='top', fill='both', expand=1) self._txt = ScrolledText(self._topframe, fg='white', bg='black', font=fnt, height=20) self._txt.pack(side='bottom', fill='both', expand=1) self._txt.bind("<KeyPress>", self._ignoreThis) self._txtbuffer = ScrolledText(self._topframe, fg='white', bg='black', font=fnt, height=20) self._txtbuffer.bind("<KeyPress-Escape>", self.escape) self._txtbuffer.bind("<KeyPress>", self._ignoreThis) self._entry.focus_set() self._initColorTags() self.dequeue() exported.hook_register("config_change_hook", self.configChangeHandler) exported.hook_register("to_user_hook", self.write) # FIXME - fix this explanation. this is just terrible. tc = config.BoolConfig( "saveinputhighlight", 0, 1, "Allows you to change the behavior of the command entry. When " "saveinputhighlight is off, we discard whatever is on the entry " "line. When it is on, we will retain the contents allowing you " "to press the enter key to do whatever you typed again.") exported.add_config("saveinputhighlight", tc) self._quit = 0 def runui(self): global HELP_TEXT exported.add_help("tkui", HELP_TEXT) exported.write_message("For tk help type \"#help tkui\".") exported.add_command("colorcheck", colorcheck_cmd) # run the tk mainloop here self._tk.mainloop() def wantMainThread(self): # The tkui needs the main thread of execution so we return # a 1 here. return 1 def quit(self): if not self._quit: self._quit = 1 self._topframe.quit() def dequeue(self): qsize = self._event_queue.qsize() if qsize > 10: qsize = 10 for i in range(qsize): ev = self._event_queue.get_nowait() ev.execute(self) self._tk.after(25, self.dequeue) def settitle(self, title=""): """ Sets the title bar to the Lyntin title plus the given string. @param title: the title to set @type title: string """ if title: title = constants.LYNTINTITLE + title else: title = constants.LYNTINTITLE self._event_queue.put(_TitleEvent(self._tk, title)) def removeWindow(self, windowname): """ This removes a NamedWindow from our list of NamedWindows. @param windowname: the name of the window to write to @type windowname: string """ if self._windows.has_key(windowname): del self._windows[windowname] def writeWindow(self, windowname, message): """ This writes to the window named "windowname". If the window does not exist, we spin one off. It handles ansi text and messages just like writing to the main window. @param windowname: the name of the window to write to @type windowname: string @param message: the message to write to the window @type message: string or Message instance """ self._event_queue.put(_WriteWindowEvent(windowname, message)) def writeWindow_internal(self, windowname, message): if not self._windows.has_key(windowname): self._windows[windowname] = NamedWindow(windowname, self, self._tk) self._windows[windowname].write(message) def _ignoreThis(self, tkevent): """ This catches keypresses from the history buffer.""" # kludge so that ctrl-c doesn't get caught allowing windows # users to copy the buffer.... if tkevent.keycode == 17 or tkevent.keycode == 67: return self._entry.focus() if tkevent.char: # we do this little song and dance so as to pass events # we don't want to deal with to the entry widget essentially # by creating a new event and tossing it in the event list. # it only sort of works--but it's the best code we've got # so far. args = ('event', 'generate', self._entry, "<KeyPress>") args = args + ('-rootx', tkevent.x_root) args = args + ('-rooty', tkevent.y_root) args = args + ('-keycode', tkevent.keycode) args = args + ('-keysym', tkevent.keysym) self._tk.tk.call(args) return "break" def pageUp(self): """ Handles prior (Page-Up) events.""" if self._viewhistory == 0: self._txtbuffer.pack(side='top', fill='both', expand=1) self._viewhistory = 1 self._txtbuffer.delete("1.0", "end") lotofstuff = self._txt.get('1.0', 'end') self._txtbuffer.insert('end', lotofstuff) for t in self._txt.tag_names(): taux = None tst = 0 for e in self._txt.tag_ranges(t): if tst == 0: taux = e tst = 1 else: tst = 0 self._txtbuffer.tag_add(t, str(taux), str(e)) self._txtbuffer.yview('moveto', '1') if os.name != 'posix': self._txtbuffer.yview('scroll', '20', 'units') self._tk.update_idletasks() self._txt.yview('moveto', '1.0') if os.name != 'posix': self._txt.yview('scroll', '220', 'units') else: # yscroll up stuff self._txtbuffer.yview('scroll', '-15', 'units') def pageDown(self): """ Handles next (Page-Down) events.""" if self._viewhistory == 1: # yscroll down stuff self._txtbuffer.yview('scroll', '15', 'units') def escape(self, tkevent): """ Handles escape (Escape) events.""" if self._viewhistory == 1: self._txtbuffer.forget() self._viewhistory = 0 else: self._entry.clearInput() def configChangeHandler(self, args): """ This handles config changes including mudecho. """ name = args["name"] newvalue = args["newvalue"] if name == "mudecho": if newvalue == 1: # echo on self._do_i_echo = 1 self._entry.configure(show='') else: # echo off self._do_i_echo = 0 self._entry.configure(show='*') def _yadjust(self): """Handles y scrolling after text insertion.""" self._txt.yview('moveto', '1') # if os.name != 'posix': self._txt.yview('scroll', '20', 'units') def _clipText(self): """ Scrolls the text buffer up so that the new text written at the bottom of the text buffer can be seen. """ temp = self._txt.index("end") ind = temp.find(".") temp = temp[:ind] if (temp.isdigit() and int(temp) > 800): self._txt.delete("1.0", "100.end") def write(self, args): """ This writes text to the text buffer for viewing by the user. This is overridden from the 'base.BaseUI'. """ self._event_queue.put(_OutputEvent(args)) def write_internal(self, args): mess = args["message"] if type(mess) == types.StringType: mess = message.Message(mess, message.LTDATA) line = mess.data ses = mess.session if line == '' or self.showTextForSession(ses) == 0: return color, leftover = buffer_write(mess, self._txt, self._currcolors, self._unfinishedcolor) if mess.type == message.MUDDATA: self._unfinishedcolor[ses] = leftover self._currcolors[ses] = color self._clipText() self._yadjust() def convertColor(self, name): """ Tk has this really weird color palatte. So I switched to using color names in most cases and rgb values in cases where I couldn't find a good color name. This method allows me to specify either an rgb or a color name and it converts the color names to rgb. @param name: either an rgb value or a name @type name: string @returns: the rgb color value @rtype: string """ if name.startswith("#"): return name rgb = self._tk._getints( self._tk.tk.call('winfo', 'rgb', self._txt, name)) rgb = "#%02x%02x%02x" % (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) print name, "converted to: ", rgb return rgb def _initColorTags(self): """ Sets up Tk tags for the text widget (fg/bg/u).""" for ck in fg_color_codes.keys(): color = self.convertColor(fg_color_codes[ck]) self._txt.tag_config(ck, foreground=color) self._txtbuffer.tag_config(ck, foreground=color) for ck in bg_color_codes.keys(): self._txt.tag_config(ck, background=bg_color_codes[ck]) self._txtbuffer.tag_config(ck, background=bg_color_codes[ck]) self._txt.tag_config("u", underline=1) self._txtbuffer.tag_config("u", underline=1) def colorCheck(self): """ Goes through and displays all the combinations of fg and bg with the text string involved. Purely for debugging purposes. """ fgkeys = ['30', '31', '32', '33', '34', '35', '36', '37'] bgkeys = ['40', '41', '42', '43', '44', '45', '46', '47'] self._txt.insert('end', 'color check:\n') for bg in bgkeys: for fg in fgkeys: self._txt.insert('end', str(fg), (fg, bg)) self._txt.insert('end', str("b" + fg), ("b" + fg, bg)) self._txt.insert('end', '\n') for fg in fgkeys: self._txt.insert('end', str(fg), (fg, "b" + bg)) self._txt.insert('end', str("b" + fg), ("b" + fg, "b" + bg)) self._txt.insert('end', '\n') self._txt.insert('end', '\n') self._txt.insert('end', '\n')
class NamedWindow: """ This creates a window for the Tkui which you can then write to programmatically. This allows modules to spin off new named windows and write to them. """ def __init__(self, windowname, master, partk): """ Initializes the window @param windowname: the name of the new window @type windowname: string @param master: the main tk window @type master: toplevel """ self._parent = master self._tk = Toplevel(partk) self._windowname = windowname # map of session -> (bold, foreground, background) self._currcolors = {} # ses -> string self._unfinishedcolor = {} self._do_i_echo = 1 self._tk.geometry("500x300") self._tk.title("Lyntin -- " + self._windowname) self._tk.protocol("WM_DELETE_WINDOW", self.close) if os.name == "posix": fontname = "Courier" else: fontname = "Fixedsys" fnt = tkFont.Font(family=fontname, size=12) self._txt = ScrolledText(self._tk, fg="white", bg="black", font=fnt, height=20) self._txt.pack(side=TOP, fill=BOTH, expand=1) # handles improper keypresses self._txt.bind("<KeyPress>", self._ignoreThis) # initialize color tags self._initColorTags() def convertColor(self, name): """ Tk has this really weird color palatte. So I switched to using color names in most cases and rgb values in cases where I couldn't find a good color name. This method allows me to specify either an rgb or a color name and it converts the color names to rgb. @param name: either an rgb value or a name @type name: string @returns: the rgb color value @rtype: string """ if name[0] == "#": return name rgb = self._tk._getints( self._tk.tk.call('winfo', 'rgb', self._txt, name)) rgb = "#%02x%02x%02x" % (rgb[0] / 256, rgb[1] / 256, rgb[2] / 256) print name, "converted to: ", rgb return rgb def _initColorTags(self): """ Sets up Tk tags for the text widget (fg/bg).""" for ck in fg_color_codes.keys(): color = self.convertColor(fg_color_codes[ck]) self._txt.tag_config(ck, foreground=color) for ck in bg_color_codes.keys(): self._txt.tag_config(ck, background=bg_color_codes[ck]) self._txt.tag_config("u", underline=1) def _ignoreThis(self, tkevent): """ This catches keypresses to this window. """ return "break" def close(self): """ Closes and destroys references to this window. """ self._parent.removeWindow(self._windowname) self._tk.destroy() def _yadjust(self): """Handles y scrolling after text insertion.""" self._txt.yview('moveto', '1') # if os.name != 'posix': self._txt.yview('scroll', '20', 'units') def _clipText(self): """ Scrolls the text buffer up so that the new text written at the bottom of the text buffer can be seen. """ temp = self._txt.index("end") ind = temp.find(".") temp = temp[:ind] if (temp.isdigit() and int(temp) > 800): self._txt.delete("1.0", "100.end") def write(self, msg): """ This writes text to the text buffer for viewing by the user. This is overridden from the 'base.BaseUI'. """ if type(msg) == types.TupleType: msg = msg[0] if type(msg) == types.StringType: msg = message.Message(msg, message.LTDATA) line = msg.data ses = msg.session if line == '': return color, leftover = buffer_write(msg, self._txt, self._currcolors, self._unfinishedcolor) if msg.type == message.MUDDATA: self._unfinishedcolor[ses] = leftover self._currcolors[ses] = color self._clipText() self._yadjust()
class Globby_Text_Editor(tk.Frame): def __init__(self, parent_widget, settings): # some initial values # TODO this Values are obsolete since Project_Settings covers them # --> self.settings.projects_path self.hash_opened_filename = None self.opened_filename = None self.settings = settings self.edit_button_list=[ {'text':'new page', 'cmd':self.on_new_page, 'keytxt':'CTRL+n','hotkey':'<Control-n>'}, {'text':'del page', 'cmd':self.on_del_page, 'keytxt':'CTRL+n','hotkey':'<DELETE>'} , {'text':'save', 'cmd':self.on_save, 'keytxt':'CTRL+s','hotkey':'<Control-s>'}, {'text':'undo', 'cmd':self.on_undo, 'keytxt':'CTRL+z','hotkey':'<Control-z>'}, {'text':'redo', 'cmd':self.on_redo, 'keytxt':'CTRL+y','hotkey':'<Control-y>'}] self.syntax_button_list=[ {'text':'**bold**', 'cmd':self.on_tag_insert, 'open_tag':'**', 'close_tag':'**','keytxt':'CTRL+b','hotkey':'<Control-b>'}, {'text':'//italic//', 'cmd':self.on_tag_insert, 'open_tag':'//', 'close_tag':'//', 'keytxt':'CTRL+i','hotkey':'<Control-i>'}, {'text':'__underline__', 'cmd':self.on_tag_insert, 'open_tag':'__', 'close_tag':'__', 'keytxt':'CTRL+u','hotkey':'<Control-u>'}, {'text':'[Link]', 'cmd':self.on_tag_insert, 'open_tag':'[', 'close_tag':']', 'keytxt':'CTRL+l','hotkey':'<Control-l>'}, {'text':'¸¸sub¸¸', 'cmd':self.on_tag_insert, 'open_tag':'¸¸', 'close_tag':'¸¸', 'keytxt':'CTRL+d','hotkey':'<Control-d>'}, {'text':'^^upper^^', 'cmd':self.on_tag_insert, 'open_tag':'^^', 'close_tag':'^^', 'keytxt':'CTRL+q','hotkey':'<Control-q>'}, {'text':'-~smaller~-', 'cmd':self.on_tag_insert, 'open_tag':'-~', 'close_tag':'~-', 'keytxt':'CTRL+w','hotkey':'<Control-w>'}, {'text':'+~bigger~+', 'cmd':self.on_tag_insert, 'open_tag':'+~', 'close_tag':'~+', 'keytxt':'CTRL+e','hotkey':'<Control-e>'}, {'text':'~~strike_thru~~', 'cmd':self.on_tag_insert, 'open_tag':'~~', 'close_tag':'~~', 'keytxt':'CTRL+t','hotkey':'<Control-t>'} ] # build Widgets tk.Frame.__init__(self, parent_widget) self.pack(fill=tk.BOTH, expand=tk.YES) #self.baseframe = tk.Frame(parent_widget) #self.baseframe.pack(fill=tk.BOTH, expand=tk.YES) self.editor() self.button_frame() # start tracking text changes inside the editfield thread.start_new_thread(self.on_txt_changes, ('',)) def editor(self): """ combine some Widgets to an enhanced editor (incl. Scrollbar) --> self.text the text widget itself --> self.opened_file_label Label on top of the editfield to show the name of the current opened File It can be used to show textchanges """ # build widgets self.txtfrm = tk.Frame(self) self.txtfrm.pack(fill=tk.BOTH, side=tk.LEFT, expand=tk.YES) self.opened_file_label = tk.Label(self.txtfrm, text="No File chosen") self.opened_file_label.pack(fill=tk.X) self.text = ScrolledText(self.txtfrm, bg="white", undo=1, maxundo=30, wrap=tk.WORD) self.text.pack(fill=tk.BOTH, expand=tk.YES, side=tk.LEFT) self.text.insert(1.0, u"Please open a File to edit") # build first(reference -- new name??) hash for comparison on changes self.hash_opened_filename = hash(self.text.get(1.0,tk.END)) # Set focus on textwidget and move cursor to the upper left self.text.focus_set() self.text.mark_set(tk.INSERT, '0.0') # goto line self.text.see(tk.INSERT) # scroll to line def label_button_row(self, parent_widget=None, btnlst=None, start_count=0): """Build a 2 column table with a label beside each button in a row. Bind a keyboard sequence to the button command. Display this keyboard sequence on the label. todo: - think about a parameter for the widget to bind the Hotkeys - rename to: labled_button_row, draw_labled_button_row Parameter: --> parent_widget: Parent widget to place the table --> btnlst: Type: List of dicts representing a button Example: {'text':'**bold**', # displayed on the Button (string) 'cmd':self.on_tag_insert, # command 'open_tag':'**', # chars representing the beginning # of a tag for inserting (string) 'close_tag':'**', # chars representing the end # of a tag for inserting (string) 'keytxt':'CTRL+b', # displayed on the Label (string) 'hotkey':'<Control-b>'} # keyboard sequence (string) Note: The existence of 'open_tag' and 'close_tag' in btnlst decides which command is bound to the Button. If they aren't there 'cmd' must be a function without parameters!!! otherwise 'cmd' needs following parameters: otag = btn['open_tag'] ctag = btn['close_tag'] event = None # Placeholder for a keysequence --> start_count: Type: int Description: The table is relized with tkinter grid layout manager. start_count is used if there is already a grid (with a Label beside a button). start_count can add the automatic genrated buttons under the existing. In Globby_Editor it is used to put a label_button_row under a Tkinter menubutton(file choose, headlines). """ i = start_count for btn in btnlst: try: otag = btn['open_tag'] ctag = btn['close_tag'] event = None doit = lambda e=event, o=otag, c=ctag:self.on_tag_insert(e,o,c) tk.Button(parent_widget, text=btn['text'], command=doit, relief=tk.RIDGE ).grid(column=0, row=i, sticky=tk.W+tk.E) self.text.bind(btn['hotkey'],doit) except KeyError: tk.Button(parent_widget, text=btn['text'], command=btn['cmd'], relief=tk.RIDGE ).grid(column=0, row=i, sticky=tk.W+tk.E) tk.Label(parent_widget, text=btn['keytxt'], relief=tk.FLAT ).grid(column=1, row=i, sticky=tk.W) i +=1 def button_frame(self): """draws a frame to hold a edit- and syntax-buttons under each other """ self.btnfrm = tk.Frame(self) self.btnfrm.pack(fill=tk.BOTH, side=tk.LEFT) self.edit_buttons() self.syntax_buttons() def edit_buttons(self): """draws a frame with buttons for editing (save, undo, redo, open) """ # genrate a labelframe self.efrm = tk.LabelFrame(self.btnfrm, text="Edit Buttons") self.efrm.pack(fill=tk.BOTH, padx=5, pady=5) # generate a button with a pulldown menue to open a file to edit self.file_open_mbtn = tk.Menubutton(self.efrm, text='Open File') # generate the pulldown menue self.file_open_menu = tk.Menu(self.file_open_mbtn, postcommand=self.gen_file2edit_menu) # bind the pulldown menue to the menubutton self.file_open_mbtn.config(menu=self.file_open_menu, relief=tk.RIDGE) self.file_open_mbtn.grid(column=0,row=0, sticky=tk.W+tk.E) # label beside the Button to display the associated keyboard shortcut self.file_open_lbl = tk.Label(self.efrm, text='CTRL+o', relief=tk.FLAT) self.file_open_lbl.grid(column=1, row=0, sticky=tk.W+tk.E) # generate buttons as described in self.edit_button_list self.label_button_row(self.efrm, self.edit_button_list, 2) # bind keyboard shortcut to the menue self.text.bind('<Control-o>', lambda e: self.file_open_menu.tk_popup(e.x_root, e.y_root)) def gen_file2edit_menu(self): """generates a (new) menu bound to the file chooser button so every time when a project is created or deleted gen_choose_project_menu should be called """ # delete all existing menue entrys self.file_open_menu.delete(0,tk.END) proj_path = os.path.join(self.settings.projects_path, self.settings.current_project ) print "proj_path", proj_path for this_file in os.listdir(proj_path): splitted = os.path.splitext(this_file) if splitted[1] == ".txt" and splitted[0] != "menue": #print "this_file",this_file open_file = os.path.join(proj_path, this_file) do_it = lambda bla = open_file:self.on_open(bla) self.file_open_menu.add_command(label=splitted, command=do_it) def syntax_buttons(self): """draws a frame with buttons for insert (wiki)markup idea: new parameter for on_tag_insert() jump_in_between=True/False so a pulldown list for different levels of headlines arn't necessary """ # genrate a labelframe self.sfrm = tk.LabelFrame(self.btnfrm, text="Syntax Buttons") self.sfrm.pack(fill=tk.BOTH, padx=5, pady=5) # generate a button with a pulldown menue für headline Syntax self.headln_menubtn = tk.Menubutton(self.sfrm, text='= Headlines =') # generate the pulldown menue self.headln_menu = tk.Menu(self.headln_menubtn) # bind the pulldown menue to the menubutton self.headln_menubtn.config(menu=self.headln_menu, relief=tk.RIDGE) # generate menue entrys i=1 for entry in ('h1','h2','h3','h4','h5','h6'): otag = '\n\n'+'='*i+' ' ctag = ' '+'='*i+'\n\n' doit = lambda event=None, o=otag, c=ctag:self.on_tag_insert(event,o,c) self.headln_menu.add_command(label=entry, command=doit) i+=1 self.headln_menubtn.grid(column=0,row=0, sticky=tk.W+tk.E) # label beside the Button to display the associated keyboard shortcut self.headln_lbl = tk.Label(self.sfrm, text='CTRL+h', relief=tk.FLAT) self.headln_lbl.grid(column=1, row=0, sticky=tk.W+tk.E) # generate buttons as described in self.edit_button_list self.label_button_row(self.sfrm, self.syntax_button_list, 1) # bind keyboard shortcut to the menue self.text.bind('<Control-h>', lambda e: self.headln_menu.tk_popup(e.x_root, e.y_root)) def on_txt_changes(self, dummy_value=tk.NONE): """ tracks text changes inside the editfield by comparing hash values new name: visualize_txt_changes??? """ while True: new_hash = hash(self.text.get(1.0, tk.END)) if new_hash != self.hash_opened_filename: #print "changes" self.opened_file_label.configure(fg="red") else: #print "no changes" self.opened_file_label.configure(fg="black") sleep(0.2) def on_open(self, file_to_open=None): """- opens a *.txt file from project folder - generates a reference hash. - Brings the cursor to the upper left and show this position in the textfield Parameter: --> file_to_open: complete path for file to open idea: - rename file_to_open to openfile or file_to_open """ self.opened_file_to_open = file_to_open self.opened_file_label.configure(text=file_to_open) self.text.delete(1.0, tk.END) self.opened_filename = os.path.basename(file_to_open) # write file content into the editfield editfile = codecs.open(file_to_open,'r', 'utf-8') self.text.insert(1.0, editfile.read()) editfile.close() # generate reference hash for a comparison to track text changes self.hash_opened_filename = hash(self.text.get(1.0,tk.END)) self.text.edit_reset() # clear tk's undo/redo stacks self.text.focus_set() # focus to textfield self.text.mark_set(tk.INSERT, '0.0') # place cursor to upper left self.text.see(tk.INSERT) # and display this line def on_save(self): """ Safes the current edited file""" if self.opened_filename: print "on_safe_" print " self.opened_filename",self.opened_filename self.hash_opened_filename = hash(self.text.get(1.0,tk.END)) path_to_safe_file = os.path.join(self.settings.projects_path, self.settings.current_project, self.opened_filename) safefile = codecs.open(path_to_safe_file,'w', 'utf-8') safefile.write(self.text.get(1.0,tk.END)) safefile.close() self.text.edit_reset() #clear tk's undo/redo stacks else: showinfo('Globby Text Editor','No File to save \n\n' 'You need to choose a File before editing') def on_undo(self): try: # tk8.4 keeps undo/redo stacks self.text.edit_undo( ) # exception if stacks empty except tk.TclError: showinfo('Globby Text Editor', 'Nothing to undo') def on_redo(self): print "redo" try: # tk8.4 keeps undo/redo stacks self.text.edit_redo() # exception if stacks empty except tk.TclError: showinfo('Globby Text Editor', 'Nothing to redo') def on_new_page(self): """ Ask the user to name the new File, create a blank File and load it into the Editorwidget TODO: check if file with the new filename allready exists check if Filename contains Specialchars """ print "on_new_page" nfile_name = tkSimpleDialog.askstring("New File Name", "Fill in a new File Name") proj_path = os.path.join(self.settings.projects_path, self.settings.current_project) nfile_name = os.path.join(proj_path, nfile_name.strip()+'.txt') nfile = codecs.open(nfile_name, 'w', 'utf-8') current_project = self.settings.current_project infostring1 = u'# Diese Datei wurde automatisch mit ' infostring2 = u'dem Projekt "%s" erstellt' % current_project nfile.write(infostring1+infostring2 ) nfile.close() self.on_open(nfile_name) def on_del_page(self): """""" print "del page" # self.settings.current_project del_file = os.path.join(self.settings.projects_path, self.settings.current_project, self.opened_filename) del_page = askyesno("Do you really want to delete ", del_file) if del_page: #self.set_project(self.new_project_name) print "%s geloescht" % del_file os.remove(del_file) def on_tag_insert(self, event=None, open_tag=None, close_tag=None): """ inserts a (wiki)tag to the current cursor position. If there is no text marked in the editfield, open_tag and close_tag are inserted to the current cursor position behind each other and the cursor jumps in between. Otherwise the marked string is enclosed by open_tag and close_tag and inserted to the current cursor position. Here the new cursor position is right behind the complete inserted string with tags. At this moment this behavior is quite buggy :-( idea: - new parameter for on_tag_insert() jump_in_between=True/False so a pulldown list for different levels of headlines arn't necessary - rename to: on_insert_tag?? on_tag_insert Parameter: --> event # keyboard shortcut --> open_tag # string --> close_tag # string """ #print 'event',event #print 'open_tag',open_tag #print 'close_tag',close_tag ## when no String is selected: if not self.text.tag_ranges(tk.SEL): print "no String is selected" insert_point = self.text.index('insert') insertline = insert_point.split('.')[0] addit = 1 if event != None: print "event not None" addit = 2 insertrow = str(int(insert_point.split('.')[1])+len(open_tag)+addit) new_insert_point = insertline+'.'+ insertrow self.text.insert(insert_point, open_tag+''+close_tag) # place cursor to insert_point self.text.mark_set(tk.INSERT, new_insert_point) # display this position on the editfield self.text.see(tk.INSERT) ## when a String is selected: else: #print "im else" marked_text = self.text.get(self.text.index(tk.SEL_FIRST), self.text.index(tk.SEL_LAST)) replace_index = self.text.index(tk.SEL_FIRST) print "replace_index in selected", replace_index self.text.delete(self.text.index(tk.SEL_FIRST), self.text.index(tk.SEL_LAST)) self.text.insert(replace_index, open_tag+marked_text+close_tag)
class MyThing(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.textBox = ScrolledText(self, height=30, width=85) self.textBox.pack() self.addText() def leftMouseClick(self, event): print "got a click" mouseIndex = "@%d,%d" % (event.x, event.y) current = self.textBox.index(mouseIndex) row = current.split(".")[0] + ".0" rowFl = float(row) + 1.0 row = str(rowFl) target = self.rowIndex[row] target = float(target) + 12.0 target = str(target) print "moving to target", target self.textBox.see(target) def rightMouseClick(self, event): self.textBox.see("0.0") def addText(self): f = open("help.txt", 'r') lines = f.readlines() f.close() flag = 1 i = 0 sectionOrder = {} sectionHeads = {} outlines = {} targetId = 0 defaultTarget = "" tocEntries = [] lineId = 0 for line in lines: if line[0:1] == "#": tocEntries.append(line) if flag: top = lineId flag = 0 lineId += 1 self.tocEntries = tocEntries # header text header = lines[0:top] for line in header: hid = self.textBox.insert(END, line, "header") self.textBox.tag_config("header", foreground=FGCOLOR) self.textBox.tag_config("header", background=BGCOLOR) self.textBox.insert(END, "Table of Contents\n", "toc") self.textBox.tag_config("toc", foreground="red") self.textBox.tag_config("toc", background=BGCOLOR) self.textBox.insert( END, "(Left-click entry to navigate, right-click to return)\n\n", "directions") self.textBox.tag_config("directions", background=BGCOLOR) self.textBox.tag_config("directions", foreground="purple") sectionDict = {} rowIndex = {} for tocEntry in tocEntries: if tocEntry[0:2] == "##": line = "\t" + tocEntry[2:] else: line = tocEntry[1:] rowPosition = self.textBox.index(END).split(".")[0] + ".0" rowIndex[rowPosition] = "0.0" sectionDict[tocEntry] = rowPosition self.textBox.insert(END, line, "tocEntry") self.textBox.tag_bind("tocEntry", '<ButtonRelease-1>', self.leftMouseClick) self.textBox.tag_config("tocEntry", background=BGCOLOR) self.textBox.tag_config("tocEntry", foreground=hfg) for i in range(50): self.textBox.insert(END, "\n", "regular") lines = lines[top:] for line in lines: if sectionDict.has_key(line): print "section id", line position = self.textBox.index(END) tocPosition = sectionDict[line] rowIndex[tocPosition] = position line = line.replace("#", "") self.textBox.insert(END, line, "regular") self.rowIndex = rowIndex self.sectionDict = sectionDict self.textBox.see("0.0") self.textBox.bind_all('<ButtonRelease-3>', self.rightMouseClick) self.textBox.tag_config("regular", background=BGCOLOR) self.textBox.tag_config("regular", foreground="black")
class TermWindow(Frame): EOL = None ## # constructor method def __init__(self, master=None, **cnf): apply(Frame.__init__, (self, master), cnf) self.__create_widgets__() self.rxWaitPattern = "" self.localEcho = False self.logFile = None self.inQueue = Queue.Queue() self.outQueue = Queue.Queue() self.update_thread_safe() def update_thread_safe(self): while not self.inQueue.empty(): element = self.inQueue.get_nowait() if element is not None: msg, tag = element self.__display__(msg, tag) self.st_trm.update_idletasks() self.st_trm.after(100, self.update_thread_safe) ## # def __create_widgets__(self): dfl_font = 'Courier 10' # the title frame of the terminal self.f_title = Frame(self) self.f_title.pack(fill=BOTH) self.f_title.configure(FRAMEBORDER) self.shVar = StringVar() # show/hide button self.b_sh = Button(self.f_title, textvariable=self.shVar, font=dfl_font) self.b_sh.pack(side=RIGHT) self.b_sh['command'] = self.show_hide_cont # clear screen button self.b_cls = Button(self.f_title, text="CLS", font=dfl_font, underline=0) self.b_cls.pack(side=RIGHT) self.b_cls['command'] = self.clear_screen # echo on/off button self.al_echo = Label(self.f_title, text="ECHO", relief=RAISED, font=dfl_font, padx='3m', pady='1m', underline=0) self.al_echo.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_echo.bind("<Button-1>", self.__echo_handler__) # log on/off button self.al_log = Label(self.f_title, text="LOG", relief=RAISED, font=dfl_font, padx='3m', pady='1m', underline=0) self.al_log.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_log.bind("<Button-1>", self.__log_handler__) # device connect button self.al_connect = Label(self.f_title, text="CONNECT", relief=RAISED, font=dfl_font, padx='3m', pady='1m', underline=1) self.al_connect.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_connect.bind("<Button-1>", self.__connect_handler__) self.mb_macros = Menubutton(self.f_title, text="Macros", relief=RAISED) self.mb_macros.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.mb_macros.menu = Menu(self.mb_macros, tearoff=0) self.mb_macros["menu"] = self.mb_macros.menu # title of terminal window self.tVar = StringVar() self.l_title = Label(self.f_title, text="foo", font=dfl_font) self.l_title['textvariable'] = self.tVar self.l_title.pack(side=LEFT, expand=1, fill=X) self.l_title['width'] = 42 self.update_title("------ XXX ------") # frame for scrolled text window # (this frame handle needs to be kept fo show_hide_cont()) self.f_cont = Frame(self) # IO data scrolled text window self.st_trm = ScrolledText(self.f_cont, height=10, state=DISABLED, wrap=NONE) self.st_trm.pack(expand=1, fill=BOTH) self.st_trm['font'] = dfl_font self.st_trm.tag_config('E', foreground="blue") self.st_trm.tag_config('M', foreground="magenta") tframe = Frame(self.f_cont) tframe.pack(expand=0, fill=X) self.cmdVar = StringVar() self.ent_trm = Entry(tframe, textvariable=self.cmdVar, font=dfl_font) self.ent_trm.pack(side=LEFT, expand=1, fill=X) self.ent_trm.bind("<Control-l>", self.__log_handler__) self.ent_trm.bind("<Control-e>", self.__echo_handler__) self.ent_trm.bind("<Control-o>", self.__connect_handler__) self.ent_trm.bind("<Control-c>", self.clear_screen) self.ent_trm.bind("<Control-x>", self.show_hide_cont) self.ent_trm.bind("<Control-m>", lambda *args: self.do_macro("M")) self.ent_trm.bind("<KeyPress>", self.__input_handler__) self.gui_elements = [ self.b_sh, self.b_cls, self.al_echo, self.al_log, self.al_connect, self.mb_macros, self.l_title, self.st_trm, self.ent_trm ] self.show_cont() def add_macro(self, id, title, text=None, function=None, params=None): if text: cmd = lambda *args: self.do_macro(text) if function: user_func = eval(function) if params: params = eval(str(params)) else: params = {} cmd = lambda *args: user_func(self, DEVICES, **params) mb = self.mb_macros.menu.add_command(label=title, command=cmd) def _configure_(self, **args): for e in self.gui_elements: e.configure(args) def __input_handler__(self, *args): for i in args: self.terminal_device_write(i.char) def terminal_device_write(self, *args): for i in args: if self.localEcho: self.display(i, "E") if len(i): if i == "\r" or i == "\n": self.device.write(self.EOL) self.display(self.EOL, "E") self.cmdVar.set("") else: self.device.write(i) self.st_trm.update_idletasks() def __echo_handler__(self, *args): if self.localEcho: self.localEcho = False self.al_echo['relief'] = RAISED self.message("Local Echo OFF") else: self.localEcho = True self.al_echo['relief'] = SUNKEN self.message("Local Echo ON") def __log_handler__(self, *args): try: do_open = self.logFile.closed logname = self.logFile.name except: do_open = True logname = "" if do_open: if self.device.logname: logname = self.device.logname self.message("logfile from config file %s " % logname) else: fd = FileDialog(self) logname = fd.go(logname) try: if self.device.logmode not in "wa": self.device.logmode = "a" _print(3, "force _a_ppend") self.logFile = open(logname, self.device.logmode) self.al_log['relief'] = SUNKEN self.message("Logging ON: %s" % self.logFile.name) except: self.message("Error open logfile: %s" % logname) else: self.message("Logging OFF: %s" % self.logFile.name) self.logFile.flush() self.logFile.close() self.al_log['relief'] = RAISED def __connect_handler__(self, *args): if self.device.isConnected: self.device.disconnect() self.al_connect['relief'] = RAISED self.message("Disconnected") else: try: self.device.connect() self.al_connect['relief'] = SUNKEN self.message("Connected") self.al_connect['fg'] = "black" except: self.device.isConnected = False self.message(str(sys.exc_info()[1])) self.al_connect['fg'] = "red" def clear_screen(self, *args): self.st_trm['state'] = NORMAL self.st_trm.delete("0.0", END) self.st_trm['state'] = DISABLED def set_device(self, device): self.device = device self.device.configure(TxQueue=self.outQueue, RxQueue=self.inQueue) self.update_title(self.device) if self.device.log: self.__log_handler__() if self.device.auto_connect: self.__connect_handler__() def update_title(self, title): self.tVar.set(title) def show_cont(self): self.shVar.set("X") self.f_cont.pack(expand=1, fill=BOTH) def hide_cont(self): self.shVar.set("+") self.f_cont.pack_forget() def show_hide_cont(self, *args): if self.shVar.get() == "X": self.hide_cont() else: self.show_cont() def do_macro(self, *args): if self.localEcho: self.display(args[0] + "\n", "E") self.device.write(args[0] + "\n") def write(self, data): self.outQueue.put((data, None)) def message(self, text, tag='M'): msg = "[%s:%s:%s]\n" % (time.asctime(), self.device.name, text) if self.st_trm.index(AtInsert()).find(".0") < 1: msg = "\n" + msg self.inQueue.put((msg, tag)) def display(self, text, tag=None): self.inQueue.put((text, tag)) def __display__(self, msg, tag=None): self.st_trm['state'] = NORMAL here = self.st_trm.index(AtInsert()) for d in re.split("([\r\v\t\n])", msg): if len(d): if d != '\r': self.st_trm.insert(END, d) if tag: self.st_trm.tag_add(tag, here, AtInsert()) self.st_trm.see(END) self.st_trm['state'] = DISABLED try: self.logFile.write(msg) self.logFile.flush() except: pass
class GUI_hcheck: # do everything! def __init__(self): self.top = Tk() self.top.title('Health Check Tool') self.top.geometry('1400x800') #------- Label,Entry defination ----------# self.l1 = Label(self.top, text="IP:").grid(row=1, column=1, sticky="w") self.e1 = Entry(self.top) self.e1.grid(row=1, column=2,sticky="ew") self.l2 = Label(self.top, text="User:"******"w") self.e2 = Entry(self.top) self.e2.grid(row=2, column=2,sticky="ew") self.l3 = Label(self.top, text="Passwd:").grid(row=3, column=1, sticky="w") self.e3 = Entry(self.top) self.e3['show'] = '*' self.e3.grid(row=3, column=2,sticky="ew") self.l4 = Label(self.top, text="Command pool:").grid(row=4, column=1, sticky="e") self.l5 = Label(self.top, text="To be run command:").grid(row=4, column=5, sticky="e") self.e4 = Entry(self.top, width=30) self.e4.grid(row=16, column=0, columnspan=3, sticky="ew") #------- Checkbutton defination ----------# self.cb1State = IntVar() self.cb1 = Checkbutton(self.top, variable=self.cb1State, text = "Always Yes", command=self.callCheckbutton) self.cb1.grid(row=21, column=16) #------- Listbox defination ----------# self.cmdfm = Frame(self.top) self.cmdsb = Scrollbar(self.cmdfm) self.cmdsb_x = Scrollbar(self.cmdfm,orient = HORIZONTAL) self.cmdsb.pack(side=RIGHT, fill=Y) self.cmdsb_x.pack(side=BOTTOM, fill=X) self.cmdls = Listbox(self.cmdfm, selectmode=EXTENDED, height=25, width=40, xscrollcommand=self.cmdsb_x.set, yscrollcommand=self.cmdsb.set) self.cmdsb.config(command=self.cmdls.yview) self.cmdsb_x.config(command=self.cmdls.xview) self.cmdls.pack(side=LEFT, fill=BOTH) self.cmdfm.grid(row=5, rowspan=10, column=0,columnspan=4,sticky="ew") self.db_file = os.path.join(os.getcwd(),'%s' % constants.DB_NAME) flist = utility.load_file(self.db_file) if flist != None: log.debug ("db file found, start load command") for element in flist: element = element.strip('\n') if element == '' or element.startswith('#'): continue self.cmdls.insert(END, element) else: log.debug ("db file doesn't existed, initail cmd") for element in constants.CMD_LIST_COMM: self.cmdls.insert(END, element) self.dirfm = Frame(self.top) self.dirsb = Scrollbar(self.dirfm) self.dirsb_x = Scrollbar(self.dirfm,orient = HORIZONTAL) self.dirsb.pack(side=RIGHT, fill=Y) self.dirsb_x.pack(side=BOTTOM, fill=X) self.dirs = Listbox(self.dirfm, selectmode=EXTENDED, height=25, width=40, xscrollcommand=self.dirsb_x.set,yscrollcommand=self.dirsb.set) self.dirsb.config(command=self.dirs.yview) self.dirsb_x.config(command=self.dirs.xview) self.dirs.pack(side=LEFT, fill=BOTH) self.dirfm.grid(row=5, rowspan=10, column=5,columnspan=4,sticky="ew") #------- Buttion defination ----------# # add command button self.b1 = Button(self.top,text=">>",width=6,borderwidth=3,relief=RAISED, command=self.move_cmd) self.b1.grid(row=7, column=4) # del command button self.b2 = Button(self.top,text="<<",width=6,borderwidth=3,relief=RAISED, command=self.del_cmd) self.b2.grid(row=8, column=4) # move up command button self.b3 = Button(self.top,text="up",width=6,borderwidth=3,relief=RAISED, command=self.up_cmd) self.b3.grid(row=7, column=10) # move down command button self.b4 = Button(self.top,text="down",width=6,borderwidth=3,relief=RAISED, command=self.down_cmd) self.b4.grid(row=8, column=10) # start command button self.b5 = Button(self.top,text="Start",bg='red',width=10,borderwidth=3,relief=RAISED, command=self.start_process) self.b5.grid(row=2, column=11) # yes button self.b6 = Button(self.top,text="Yes",width=6,borderwidth=3,relief=RAISED, command=self.set_confirm_yes) self.b6.grid(row=21, column=13) # No button self.b7 = Button(self.top,text="No",width=6,borderwidth=3,relief=RAISED, command=self.set_confirm_no) self.b7.grid(row=21, column=14) # Skip button self.b8 = Button(self.top,text="Skip",width=6,borderwidth=3,relief=RAISED, command=self.set_confirm_skip) self.b8.grid(row=21, column=15) # Add button self.b9 = Button(self.top,text="add cmd",width=10,borderwidth=3,relief=RAISED, command=self.add_command) self.b9.grid(row=15, column=1) # Del button self.b10 = Button(self.top,text="del cmd",width=10,borderwidth=3,relief=RAISED, command=self.del_command) self.b10.grid(row=15, column=2) # Manual button self.manual = False self.b11 = Button(self.top,text="Manual model",width=10,borderwidth=3,relief=RAISED, command=self.manual_mode) self.b11.grid(row=2, column=12) #------- ScrolledText defination ----------# self.sfm = Frame(self.top) self.console = ScrolledText(self.sfm,height=45, width=86,bg='black',fg='green',insertbackground='green') self.console['font'] = ('lucida console','10') self.console.bind("<Return>", self.process_input) self.console.pack() self.sfm.grid(row=4, rowspan=15, column=11,columnspan=10,sticky="ew") self.redir = redirect(self.console) sys.stdout = self.redir sys.stderr = self.redir self.fconsole = logging.StreamHandler(sys.stdout) self.fconsole.setLevel(logging.INFO) logging.getLogger('').addHandler(self.fconsole) #------- Menu defination ----------# self.menubar = Menu() # file menu self.fmenu = Menu() #self.fmenu.add_command(label = 'New',command=self.new_win) self.fmenu.add_command(label = 'Import cmd',command=self.load_cmd) self.fmenu.add_command(label = 'Export cmd',command=self.export_cmd) self.menubar.add_cascade(label = 'File', menu = self.fmenu) # edit menu self.emenu = Menu() self.cmenu = Menu() self.cvar = StringVar() for item in ['white/black', 'black/white', 'green/black']: self.cmenu.add_radiobutton(label = item, variable=self.cvar, value=item, command=self.sel_color_style) self.emenu.add_cascade(label = 'console style', menu = self.cmenu) self.emenu.add_command(label = 'reset cmd pool',command=self.reset_cmd_pool) self.menubar.add_cascade(label = 'Edit', menu = self.emenu) self.top['menu'] = self.menubar def new_win(self): #GUI_hcheck() pass def sel_color_style(self): log.debug ("select console color style: %s " % self.cvar.get()) color = self.cvar.get() if color == 'white/black': self.console.config(bg='black',fg='white',insertbackground='white') elif color == 'black/white': self.console.config(bg='white',fg='black',insertbackground='black') elif color == 'green/black': self.console.config(bg='black',fg='green',insertbackground='green') def move_cmd(self): if self.cmdls.curselection() != (): for i in self.cmdls.curselection(): if utility.elemet_exists(self.dirs.get(0,END), self.cmdls.get(i)) == None: self.dirs.insert(END,self.cmdls.get(i)) log.debug ("move command %s" % self.cmdls.get(i)) else: tkMessageBox.showwarning('Message', 'Please select at lease one item') def load_cmd(self): file = tkFileDialog.askopenfilename() flist = utility.load_file(file) if flist != None: self.dirs.delete(0,END) for element in flist: element = element.strip('\n') if element == '' or element.startswith('#'): continue if utility.elemet_exists(self.dirs.get(0,END), element) == None: self.dirs.insert(END, element) else: tkMessageBox.showerror('Message', 'import failed') def export_cmd(self): file = tkFileDialog.askopenfilename() if utility.export_file(self.dirs.get(0,END), file) == True: tkMessageBox.showinfo('Message', 'export finish') else: tkMessageBox.showerror('Message', 'export failed') def del_cmd(self): if self.dirs.curselection() != (): for i in self.dirs.curselection(): self.dirs.delete(i) else: tkMessageBox.showwarning('Message', 'Please select at lease one item') def up_cmd(self): select = self.dirs.curselection() if (len(select)) >= 2: tkMessageBox.showwarning('Message', 'Only one select is supported') return log.debug ("move up select pos: %s" % str(select)) if select != () and select != (0,): element = self.dirs.get(select) self.dirs.delete(select) self.dirs.insert((select[0] - 1), element) self.dirs.select_set(select[0] - 1) def down_cmd(self): select = self.dirs.curselection() if (len(select)) >= 2: tkMessageBox.showwarning('Message', 'Only one select is supported') return log.debug ("move down select pos: %s" % str(select)) if select != () and select != (END,): element = self.dirs.get(select) self.dirs.delete(select) self.dirs.insert((select[0] + 1), element) self.dirs.select_set(select[0] + 1) def start_process(self): log.debug ("current thread total numbers: %d" % threading.activeCount()) response = tkMessageBox.askokcancel('Message', 'Will start healthcheck, please click OK to continue, or cancel') if response == False: return th = threading.Thread(target=self.start_cmd) th.setDaemon(True) th.start() def start_cmd(self): self.manual = False cmd = list(self.dirs.get(0,END)) if len(cmd) == 0: log.info ("To be run cmd numbers none, quit...") return log.debug ("fetch cmd list from GUI: %s" % cmd) (ip, port) = utility.getipinfo(self.e1.get()) log.debug ("get ip infor-> ip: %s, port:%s" % (ip,port)) if ip == False: log.error ("Given ip infor is wrong! The right ip address: xxx.xxx.xxx.xxx or IP:port ") return self.b5.config(state=DISABLED) self.b11.config(state=DISABLED) self.console.delete('1.0',END) try: self.wf = Workflow(cmd, ip, self.e2.get(), self.e3.get(), port) self.wf.start() except: pass self.b5.config(state=NORMAL) self.b11.config(state=NORMAL) def manual_mode(self): cmd = [] (ip, port) = utility.getipinfo(self.e1.get()) log.debug ("get ip infor-> ip: %s, port:%s" % (ip,port)) if ip == False: log.error ("Given ip infor is wrong! The right ip address: xxx.xxx.xxx.xxx or IP:port ") return self.wf_manual = Workflow(cmd, ip, self.e2.get(), self.e3.get(), port) if self.wf_manual.setup_ssh(self.wf_manual.hostip) == False: log.error ("\nssh setup error! please check ip/user/passwd and network is okay") del self.wf_manual return self.console.delete('1.0',END) self.console.insert(END, "Switch to manual mode...\n\ Please input command directly after \">>> \"\n\n") self.prompt = ">>> " self.insert_prompt() self.manual = True self.b5.config(state=DISABLED) self.b11.config(state=DISABLED) def set_confirm_yes(self): try: self.wf.set_confirm('Yes') except AttributeError: log.debug("wf doesn't existed") def set_confirm_no(self): try: self.wf.set_confirm('No') except AttributeError: log.debug("wf doesn't existed") def set_confirm_skip(self): try: self.wf.set_confirm('Skip') except AttributeError: log.debug("wf doesn't existed") def callCheckbutton(self): try: if self.cb1State.get() == 1: self.wf.set_automatic(5) else: self.wf.set_automatic(None) except AttributeError: log.debug("wf doesn't existed") self.cb1.deselect() tkMessageBox.showwarning('Message', 'please press start button first') def saveinfo(self): pass def add_command(self): item = self.e4.get() if item != '': if utility.elemet_exists(self.cmdls.get(0,END), item) == None: self.cmdls.insert(END,item) self.cmdls.see(END) log.debug ("add new command %s" % item) self.save_command() else: tkMessageBox.showwarning('Message', 'entry can not empty') def del_command(self): if self.cmdls.curselection() != (): for i in self.cmdls.curselection(): self.cmdls.delete(i) self.save_command() else: tkMessageBox.showwarning('Message', 'Please select at lease one item') def save_command(self): if utility.export_file(self.cmdls.get(0,END), self.db_file) != True: log.error ("save command pool failed") def reset_cmd_pool(self): log.debug ("start to reset command pool list") self.cmdls.delete(0,END) for element in constants.CMD_LIST_COMM: self.cmdls.insert(END, element) self.save_command() def insert_prompt(self): c = self.console.get("end-2c") if c != "\n": self.console.insert("end", "\n") self.console.insert("end", self.prompt, ("prompt",)) #self.text.insert("end", self.prompt) # this mark lets us find the end of the prompt, and thus # the beggining of the user input self.console.mark_set("end-of-prompt", "end-1c") self.console.mark_gravity("end-of-prompt", "left") def process_input(self,event): index = self.console.index("end-1c linestart") line = self.console.get(index,'end-1c') log.debug ("last line: %s" % line) if self.manual == True: self.console.insert("end", "\n") command = self.console.get("end-of-prompt", "end-1c") command = command.strip() if command != '': if command == 'bye' or command == 'exit': log.info ("quit from manual mode...") self.wf_manual.ssh.close() self.manual = False self.b5.config(state=NORMAL) self.b11.config(state=NORMAL) return elif command == 'help': log.info ("This is used for run command on target server by ssh, for example: >>> df -h, >>> svcs -xv") elif self.wf_manual.remote_cmd(command) == 1: log.error ("command %s execute failed" % command) self.insert_prompt() self.console.see("end") # this prevents the class binding from firing, since we # inserted the newline in this method return "break" else: if line == 'Yes' or line == 'y' or line == 'yes': self.set_confirm_yes() elif line == 'No' or line == 'n' or line == 'no': self.set_confirm_no() elif line == 'Skip' or line == 's' or line == 'skip': self.set_confirm_skip() else: pass
class TermWindow(Frame): ## # constructor method def __init__(self, master = None, **cnf): apply(Frame.__init__, (self, master), cnf) self.__create_widgets__() self.rxThread = None self.rxWaitEvent = threading.Event() self.rxWaitPattern = "" self.localEcho = False self.logFile = None self.textQueue = Queue.Queue() self.update_thread_safe() def update_thread_safe(self): try: while 1: element = self.textQueue.get_nowait() if element is not None: msg,tag = element self.__display__(msg,tag) self.st_trm.update_idletasks() except Queue.Empty: pass self.st_trm.after(100, self.update_thread_safe) ## # worker function for threaded reading from the input of # the assigned device # def __thrd_reader__(self): self.waitbuf = "" while 1: try: x = self.device.read() if len(x): cmd = self.cmdVar.get() if len(cmd): self.cmdVar.set("") self.write(x) if self.rxWaitEvent.isSet(): self.waitbuf += x if self.waitbuf.find(self.rxWaitPattern) > -1: self.rxWaitEvent.clear() while not self.rxWaitEvent.isSet(): pass self.rxWaitEvent.clear() except: traceback.print_exc() time.sleep(1) else: # this infinitesimal sleep keeps the GUI update alive time.sleep(.01) def waitfor(self, pattern, maxwait=10): self.waitbuf = "" self.rxWaitPattern = pattern self.rxWaitEvent.set() self.maxwait = 10 while self.rxWaitEvent.isSet() and self.maxwait > 0: time.sleep(1) self.maxwait -= 1 if self.maxwait == 0: self.message("PatternNotFound") self.rxWaitEvent.set() ## # def __create_widgets__(self): dfl_font = 'Courier 10' # the title frame of the terminal self.f_title = Frame(self) self.f_title.pack(fill=BOTH) self.f_title.configure(FRAMEBORDER) self.shVar = StringVar() # show/hide button self.b_sh = Button(self.f_title, textvariable=self.shVar, font=dfl_font) self.b_sh.pack(side=RIGHT) self.b_sh['command'] = self.show_hide_cont # clear screen button self.b_cls = Button(self.f_title, text="CLS", font=dfl_font, underline=0) self.b_cls.pack(side=RIGHT) self.b_cls['command'] = self.clear_screen # echo on/off button self.al_echo = Label(self.f_title, text = "ECHO", relief = RAISED, font = dfl_font, padx='3m', pady='1m', underline=0) self.al_echo.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_echo.bind("<Button-1>", self.__echo_handler__) # log on/off button self.al_log = Label(self.f_title, text = "LOG", relief = RAISED, font = dfl_font, padx='3m', pady='1m', underline=0) self.al_log.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_log.bind("<Button-1>", self.__log_handler__) # device connect button self.al_connect = Label(self.f_title, text = "CONNECT", relief = RAISED, font = dfl_font, padx='3m', pady='1m', underline=1) self.al_connect.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.al_connect.bind("<Button-1>", self.__connect_handler__) self.mb_macros = Menubutton(self.f_title, text = "Macros", relief=RAISED) self.mb_macros.pack(side=RIGHT, padx=1, pady=1, fill=BOTH) self.mb_macros.menu = Menu(self.mb_macros, tearoff = 0) self.mb_macros["menu"] = self.mb_macros.menu # title of terminal window self.tVar = StringVar() self.l_title = Label(self.f_title, text="foo", font=dfl_font) self.l_title['textvariable'] = self.tVar self.l_title.pack(side=LEFT, expand=1, fill=X) self.l_title['width'] = 42 self.update_title("------ XXX ------") # frame for scrolled text window # (this frame handle needs to be kept fo show_hide_cont()) self.f_cont = Frame(self) # IO data scrolled text window self.st_trm = ScrolledText(self.f_cont, height=10, state=DISABLED, wrap=NONE) self.st_trm.pack(expand=1,fill=BOTH) self.st_trm['font'] = dfl_font self.st_trm.tag_config('E', foreground="blue") self.st_trm.tag_config('M', foreground="magenta") tframe = Frame(self.f_cont) tframe.pack(expand = 0, fill = X) self.cmdVar = StringVar() self.ent_trm = Entry(tframe, textvariable=self.cmdVar, font=dfl_font) self.ent_trm.pack(side=LEFT, expand =1, fill = X) self.ent_trm.bind("<Control-l>", self.__log_handler__) self.ent_trm.bind("<Control-e>", self.__echo_handler__) self.ent_trm.bind("<Control-o>", self.__connect_handler__) self.ent_trm.bind("<Control-c>", self.clear_screen) self.ent_trm.bind("<Control-x>", self.show_hide_cont) self.ent_trm.bind("<Control-m>", lambda *args: self.do_macro("M")) self.ent_trm.bind("<KeyPress>", self.__input_handler__) self.gui_elements = [ self.b_sh, self.b_cls, self.al_echo, self.al_log, self.al_connect, self.mb_macros, self.l_title, self.st_trm, self.ent_trm] self.show_cont() def add_macro(self, id, title, text = None, function = None, params = None): if text: cmd = lambda *args: self.do_macro(text) if function: user_func = eval(function) params = eval(str(params)) cmd = lambda *args: user_func(DEVICES, **params) mb = self.mb_macros.menu.add_command(label = title, command = cmd) def _configure_(self,**args): for e in self.gui_elements: e.configure(args) def __input_handler__(self, *args): for i in args: if self.localEcho: self.display(i.char, "E") if len(i.char): if i.char == "\r": self.device.write("\r\n") self.cmdVar.set("") else: self.device.write(i.char) def __echo_handler__(self, *args): if self.localEcho: self.localEcho = False self.al_echo['relief'] = RAISED self.message("Local Echo OFF") else: self.localEcho = True self.al_echo['relief'] = SUNKEN self.message("Local Echo ON") def __log_handler__(self, *args): try: do_open = self.logFile.closed logname = self.logFile.name except: do_open = True logname = "" if do_open: if self.device.logname: logname = self.device.logname self.message("logfile from config file %s " % logname) else: fd = FileDialog(self) logname = fd.go(logname) try: if self.device.logmode not in "wa": self.device.logmode = "a" print "force _a_ppend" self.logFile = open(logname, self.device.logmode) self.al_log['relief'] = SUNKEN self.message("Logging ON: %s" % self.logFile.name) except: self.message("Error open logfile: %s" % logname) else: self.message("Logging OFF: %s" % self.logFile.name) self.logFile.flush() self.logFile.close() self.al_log['relief'] = RAISED def __connect_handler__(self, *args): if self.device.isConnected: self.device.disconnect() self.al_connect['relief'] = RAISED self.message("Disconnected") else: try: self.device.connect() self.al_connect['relief'] = SUNKEN self.message("Connected") self.al_connect['fg'] = "black" except: self.device.isConnected = False self.message( str(sys.exc_info()[1]) ) self.al_connect['fg'] = "red" def clear_screen(self, *args): self.st_trm['state'] = NORMAL self.st_trm.delete("0.0",END) self.st_trm['state'] = DISABLED def set_device(self,device): self.device = device self.rxThread = threading.Thread(target = self.__thrd_reader__) # if a thread is not a daemon, the program needs to join all self.rxThread.setDaemon(1) print "**",self.device, self.device.name self.rxThread.setName("GUI_RX_%s" % self.device.name) self.rxThread.start() self.update_title(self.device) # if self.device.echo: self.localEcho = 0 self.__echo_handler__() if self.device.log: self.__log_handler__() if self.device.auto_connect: self.__connect_handler__() def update_title(self, title): self.tVar.set(title) def show_cont(self): self.shVar.set("X") self.f_cont.pack(expand=1,fill=BOTH) def hide_cont(self): self.shVar.set("+") self.f_cont.pack_forget() def show_hide_cont(self, *args): if self.shVar.get() == "X": self.hide_cont() else: self.show_cont() def do_macro(self, *args): if self.localEcho: self.display(args[0] + "\n", "E") self.device.write(args[0]+ "\n") def write(self, data): self.textQueue.put((data, None)) def message(self, text, tag='M'): msg = "[%s:%s:%s]\n" % (time.asctime(),self.device.name, text) if self.st_trm.index(AtInsert()).find(".0") < 1: msg = "\n" + msg self.textQueue.put((msg, tag)) def display(self, text, tag = None): self.textQueue.put((text, tag)) def __display__(self, msg, tag = None): self.st_trm['state'] = NORMAL here = self.st_trm.index(AtInsert()) for d in re.split("([\r\v\t\n])", msg): if len(d): if d != '\r': self.st_trm.insert(END, d) if tag: self.st_trm.tag_add(tag, here, AtInsert()) self.st_trm.see(END) self.st_trm['state'] = DISABLED try: self.logFile.write(msg) self.logFile.flush() except: pass