class ChatClient: client_socket = None def __init__(self, ip, port): self.initialize_socket(ip, port) self.initialize_gui() self.listen_thread() def initialize_socket(self, ip, port): self.client_socket = socket(AF_INET, SOCK_STREAM) remote_ip = ip remote_port = port self.client_socket.connect((remote_ip, remote_port)) def send_chat(self): senders_name = self.name_widget.get().strip() + ":" data = self.enter_text_widget.get(1.0, 'end').strip() message = (senders_name + data).encode('utf-8') self.chat_transcript_area.insert('end', message.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) self.client_socket.send(message) self.enter_text_widget.delete(1.0, 'end') return 'break' def initialize_gui(self): self.root = Tk() fr = [] for i in range(0, 5): fr.append(Frame(self.root)) fr[i].pack(fill=BOTH) self.name_label = Label(fr[0], text='username') self.recv_label = Label(fr[1], text='received message') self.send_label = Label(fr[3], text='sending message') self.send_btn = Button(fr[3], text='전송', command=self.send_chat) self.chat_transcript_area = ScrolledText(fr[2], height=20, width=60) self.enter_text_widget = ScrolledText(fr[4], height=5, width=60) self.name_widget = Entry(fr[0], width=15) self.name_label.pack(side=LEFT) self.name_widget.pack(side=LEFT) self.recv_label.pack(side=LEFT) self.send_btn.pack(side=RIGHT, padx=20) self.chat_transcript_area.pack(side=LEFT, padx=2, pady=2) self.send_label.pack(side=LEFT) self.enter_text_widget.pack(side=LEFT, padx=2, pady=2) def listen_thread(self): t = Thread(target=self.receive_message, args=(self.client_socket, )) t.start() def receive_message(self, so): while True: buf = so.recv(256) if not buf: break self.chat_transcript_area.insert('end', buf.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) so.close()
class ConsoleUi(logging.Handler): def __init__(self, parent, update_interval=50, process_lines=500): logging.Handler.__init__(self) self.update_interval = update_interval self.process_lines = process_lines self.parent = parent #self.after(self.update_interval, self.fetch_lines) # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(self.parent, state='disabled', height=20) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orangered') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) self.scrolled_text.tag_config('OTHER', foreground='blue') #ConsoleUi.display = self.display ConsoleUi.displayText = self.displayText ConsoleUi.clearDisp = self.clearDisp def emit(self, record): msg = self.format(record) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n', record.levelname) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def clearDisp(self, *args): print('clearDisp') self.scrolled_text.configure(state='normal') self.scrolled_text.delete(0.0, tk.END) self.scrolled_text.configure(state='disabled') def displayText(self, *args): # for text files text = ' '.join([str(a) for a in args]) print(text, end='') self.scrolled_text.configure(state='normal') if ' ERROR ' in text: tag = 'ERROR' elif ' DEBUG ' in text: tag = 'DEBUG' elif ' INFO ' in text: tag = 'INFO' elif ' WARNING ' in text: tag = 'WARNING' elif ' CRITICAL ' in text: tag = 'CRITICAL' else: tag = 'OTHER' text = text+'\n' self.scrolled_text.insert(tk.END, text, tag) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) self.scrolled_text.update_idletasks()
class ChatClient: # sock = None def __init__(self, ip, port): self.sock = socket(AF_INET, SOCK_STREAM) self.sock.connect((ip, port)) self.initialize_gui() self.receiving_thread() def initialize_gui(self): # widget 배치 및 초기화 self.root = Tk() fr = [] for i in range(0, 5): fr.append(Frame(self.root)) fr[i].pack(fill=BOTH) self.name_label = Label(fr[0], text='사용자 이름') self.recv_label = Label(fr[1], text='수신 메시지 : ') self.send_label = Label(fr[3], text='송신 메시지 : ') self.send_btn = Button(fr[3], text='전송', command=self.send_chat) self.quit_btn = Button(fr[3], text='종료', command=self.root.quit) self.chat_transcript_area = ScrolledText(fr[2], height=20, width=60) self.enter_text_widget = ScrolledText(fr[4], height=5, width=60) self.name_widget = Entry(fr[0], width=15) self.name_label.pack(side=LEFT) self.name_widget.pack(side=LEFT) self.recv_label.pack(side=LEFT) self.send_btn.pack(side=RIGHT, padx=40) self.quit_btn.pack(side=RIGHT, padx=20) self.chat_transcript_area.pack(side=LEFT, padx=2, pady=2) self.send_label.pack(side=LEFT) self.enter_text_widget.pack(side=LEFT, padx=2, pady=2) def send_chat(self): # 메세지 전송하는 콜백함수 sender_name = self.name_widget.get().strip() + ':' data = self.enter_text_widget.get(1.0, 'end').strip() message = (sender_name + data).encode('utf-8') self.chat_transcript_area.insert('end', message.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) self.sock.send(message) self.enter_text_widget.delete(1.0, 'end') return 'break' def receiving_thread(self): # 데이터 수신 Thread 생성, 시작 t = Thread(target=self.receive_message, args=(self.sock, )) t.start() def receive_message(self, sock): while True: buf = sock.recv(256) if not buf: break self.chat_transcript_area.insert('end', buf.decode('utf-8') + '\n') self.chat_transcript_area.yview(END) socket.close()
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame): self.frame = frame self.input_start_idx = tk.END # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state='disabled', height=12) self.scrolled_text.pack(expand=True, fill=tk.BOTH) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='dark orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) self.scrolled_text.bind('<Key>', self.key_press) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter('%(asctime)s:\t%(message)s', datefmt='%H:%M:%S') self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self, record): msg = record.getMessage() self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n', record.levelname) # self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) self.scrolled_text.mark_set('input_start', 'end-1c') self.scrolled_text.mark_gravity('input_start', tk.LEFT) def poll_log_queue(self): while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) # Check every 100ms if there is a new message in the queue to display self.frame.after(100, self.poll_log_queue) def key_press(self, event): """Function used to send any inputs to the input_queue when the return key is pressed""" if event.char == '\r': user_input = self.scrolled_text.get('input_start', 'end-1c').strip() input_queue.put(user_input) self.scrolled_text.mark_set('input_start', 'end-1c')
async def update_conversation_history(panel: ScrolledText, messages_queue): while True: msg = await messages_queue.get() panel['state'] = 'normal' if panel.index('end-1c') != '1.0': panel.insert('end', '\n') panel.insert('end', msg) if panel.vbar.get()[1] == 1.0: panel.yview(tk.END) panel['state'] = 'disabled'
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget.""" def __init__(self, frame, simple_time=False): self.frame = frame # Create a ScrolledText wdiget if simple_time: font='80' else: font='' self.scrolled_text = ScrolledText(frame, state='disabled', height=39) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black', font=font) self.scrolled_text.tag_config('DEBUG', foreground='gray', font=font) self.scrolled_text.tag_config('WARNING', foreground='orange', font=font) self.scrolled_text.tag_config('ERROR', foreground='red', font=font) self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1, font=font) # Create a logging handler using a queue self.log_queue = Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter('%(asctime)s: %(message)s') if simple_time is True: formatter = logging.Formatter("%(asctime)s: %(message)s", "%H:%M:%S") self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n', record.levelname) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except Queue_Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue) def pass_logger(self, in_logger): in_logger.addHandler(self.queue_handler) def set_levels(self, levels): self.queue_handler.addFilter(MyFilter(levels))
class LoggingWindow: # Based on: https://github.com/beenje/tkinter-logging-text-widget def __init__(self, master): self.master = master self.scrolled_text = ScrolledText(master=master, state='disabled', height=15) self.scrolled_text.grid(row=0, column=0) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') # Get the logger self.logger = logging.getLogger() self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter( '%(asctime)s : %(levelname)s : %(message)s') self.queue_handler.setFormatter(formatter) self.logger.addHandler(self.queue_handler) # Start polling messages from the queue self.master.after(100, self.poll_log_queue) self.autoscroll = tk.BooleanVar() tk.Checkbutton(master, text='Autoscroll Log', variable=self.autoscroll).\ grid(row=1, column=0, sticky=tk.W) self.autoscroll.set(True) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n', record.levelname) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom if self.autoscroll.get(): self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.master.after(100, self.poll_log_queue)
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame): self.frame = frame # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state='disabled', height=12) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='white', background='green') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='yellow', background='purple') self.scrolled_text.tag_config('ERROR', foreground='black', background='yellow') self.scrolled_text.tag_config('CRITICAL', foreground='white', background='red', underline=1) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter('%(asctime)s: %(message)s', "%d-%m %H:%M") self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n', record.levelname) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue)
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame, master): self.frame = frame # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state='disabled', height=12) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter('%(message)s') self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) self.scrolled_text.pack(fill=BOTH, expand=1) self.button_quit = Button(self.frame, text="Quit", fg="red", command=master.quit) self.button_quit.pack(anchor=NE, side=RIGHT) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(END, msg + '\n', record.levelname) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue)
class ConsoleWindow(tk.Frame): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(self, height=7, state="disabled") self.scrolled_text.pack(expand=True, fill="both") self.scrolled_text.configure(font="TkFixedFont") self.scrolled_text.tag_config("INFO", foreground="green") self.scrolled_text.tag_config("DEBUG", foreground="gray") self.scrolled_text.tag_config("WARNING", foreground="orange") self.scrolled_text.tag_config("ERROR", foreground="red") self.scrolled_text.tag_config("CRITICAL", foreground="red", underline=1) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter("%(asctime)s: %(message)s") self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.after(100, self.poll_log_queue) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state="normal") self.scrolled_text.insert(tk.END, msg + "\n", record.levelname) self.scrolled_text.configure(state="disabled") self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.after(100, self.poll_log_queue) def close(self): pass
class ConsoleLog: def __init__(self, frame): self.frame = frame self.scrolled_text = ScrolledText(self.frame, height=10, state="disabled", bg="dodger blue") self.scrolled_text.grid(row=0, columnspan=6) self.scrolled_text.configure(font="tkFixedFont", state="normal") self.scrolled_text.tag_config("INFO", foreground="black") self.scrolled_text.tag_config("WARNING", foreground="OrangeRed4") self.scrolled_text.tag_config("DEBUG", foreground='purple4') self.scrolled_text.tag_config("ERROR", foreground='red') self.scrolled_text.tag_config("CRITICAL", foreground="red4", underline="1") # Create a loggin handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter('%(asctime)s: %(message)s', datefmt="%I:%M:%S %p") self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) self.frame.after(100, self.poll_log_queue) def display_log(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state="normal") self.scrolled_text.insert(END, msg + '\n', record.levelname) self.scrolled_text.configure(state="disabled") # autoscroll to bottom self.scrolled_text.yview(END) def poll_log_queue(self): # Check every 100ms if there is a new message to display in the queue while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: print(record) self.display_log(record) self.frame.after(100, self.poll_log_queue)
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame): self.frame = frame # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state="disabled", height=12) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font="TkFixedFont") self.scrolled_text.tag_config("INFO", foreground="black") self.scrolled_text.tag_config("DEBUG", foreground="gray") self.scrolled_text.tag_config("WARNING", foreground="orange") self.scrolled_text.tag_config("ERROR", foreground="red") self.scrolled_text.tag_config("CRITICAL", foreground="red", underline=1) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) formatter = logging.Formatter("%(asctime)s: %(message)s") self.queue_handler.setFormatter(formatter) logger.addHandler(self.queue_handler) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self, record): msg = self.queue_handler.format(record) self.scrolled_text.configure(state="normal") self.scrolled_text.insert(tk.END, msg + "\n", record.levelname) self.scrolled_text.configure(state="disabled") # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = self.log_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue)
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame): self.frame = frame # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state='disabled', height=12) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) # Create a logging handler using a queue self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self): msg = self.queue_handler self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg + '\n') self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def poll_log_queue(self): print("checked") # Check every 100ms if there is a new message in the queue to display while True: try: print("OOOOOOOOOin", queue.Empty()) record = self.log_queue.get(block=False) except queue.Empty: break else: self.display() self.frame.after(100, self.poll_log_queue)
class Log: def __init__(self, frame): self.frame = frame self.scrolled_text = ScrolledText(frame, state="disabled", height=23) # sticky -> what to do if the cell is larger than widget self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font="TkFixedFont") # queue -> logging handler self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) logger.addHandler(self.queue_handler) # msg --> formatted_msg formatter = logging.Formatter(fmt='%(asctime)s: %(message)s', datefmt="%H:%M:%S") self.queue_handler.setFormatter(formatter) # start checking queue self.frame.after(0, self.check_queue) def push(self, text): # pushing messages into the frame msg = self.queue_handler.format(text) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(END, msg + '\n') self.scrolled_text.configure(state='disabled') self.scrolled_text.yview(END) # check queue constantly def check_queue(self): while True: try: text = self.log_queue.get( block=False) # block=False --> don't wait for items except queue.Empty: break else: self.push( text ) # push the text to the frame if the queue was not empty # check queue under 100ms frequency(human reaction 200~250ms) self.frame.after(100, self.check_queue)
class Log: def __init__(self, frame): self.frame = frame self.scrolled_text = ScrolledText(frame, state="disabled", height=12) # sticky -> what to do if the cell is larger than widget self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font="TkFixedFont") # queue -> logging handler self.log_queue = queue.Queue() self.queue_handler = QueueHandler(self.log_queue) logger.addHandler(self.queue_handler) # msg --> formatted_msg formatter = logging.Formatter('%(asctime)s: %(message)s') self.queue_handler.setFormatter(formatter) # start checking queue self.frame.after(0, self.check_queue) def push(self, text): # pushing messages into the frame msg = self.queue_handler.format(text) self.scrolled_text.configure(state='normal') self.scrolled_text.insert(END, msg+'\n') self.scrolled_text.configure(state='disabled') self.scrolled_text.yview(END) def check_queue(self): # check queue under 500ms frequency while True: try: text = self.log_queue.get(block=False) except queue.Empty: break else: self.push(text) self.frame.after(500, self.check_queue)
class Logger(Frame): ERROR, BORRING, EVENT, NORMAL = "error", "borring", "event", None def __init__(self, *args, **kw): text_height = kw.pop("text_height", 25) Frame.__init__(self, *args, **kw) self.deque = deque() self.txt = ScrolledText(self, wrap=WORD, state=DISABLED, relief=SUNKEN, height=text_height) self.txt.grid(column=0, row=0, sticky=(N, W, E, S)) self.txt.configure(LoggerColors.default) for k, v in LoggerColors.tags.items(): self.txt.tag_configure(k, v) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self.after(100, self._toscreen) def __call__(self, *args): self.deque.appendleft(args) def _toscreen(self): if len(self.deque): self.txt["state"] = NORMAL while(len(self.deque)): self.txt.insert( END, "\n[{}] ".format(strftime("%H:%M:%S")), "time", *self.deque.pop()) self.txt.yview(END) self.txt["state"] = DISABLED self.after(100, self._toscreen)
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() fnt = tkinter.font.Font(family="FixedSys", size=10) 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 windowname in self._windows: 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 windowname not in self._windows: 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) == bytes: mess = message.Message(mess, message.LTDATA) elif "window" in mess.hints: self.writeWindow_internal(mess.hints["window"], mess) return 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" % (old_div(rgb[0], 256), old_div( rgb[1], 256), old_div(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 list(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 list(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 GuiPart: def __init__(self, master, queue,queue2,queue3,endCommand): self.first_click = True; self.prefix1='Server says:'#Add the prefix for good display self.prefix2='Client says:' #Cause tkinter is not safe in thread, So use queue #The Python Queue class has been specifically designed to be thread-safe #in a multi-producer, multi-consumer environment. self.queue1 = queue self.queue2 = queue2 self.queue3 = queue3 # Set up the GUI master.wm_title("Chat Server") master.resizable('1','1') self.ui_messages = ScrolledText( master=master, wrap=tkinter.WORD, width=50, # In chars height=25) # In chars self.ui_input = tkinter.Text( master=master, wrap=tkinter.WORD, width=50, height=4) # Bind the button-1 click of the Entry to the handler self.ui_input.bind('<Button-1>', self.eventInputClick) self.ui_button_send = tkinter.Button( master=master, text="Send", command=self.sendMsg) self.ui_button_file = tkinter.Button( master=master, text="File", command=self.sendFile) # Compute display position for all objects self.ui_messages.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_input.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_button_send.pack(side=tkinter.LEFT) self.ui_button_file.pack(side=tkinter.RIGHT) # Add more GUI stuff here print("Starting serverUI...") self.ui_messages.insert(tkinter.END, "Adding a message to the text field...\n") self.ui_input.insert(tkinter.END, "<Enter message>") def processIncoming(self): """ Handle all the messages currently in the queue (if any). """ while self.queue1.qsize(): try: msg = self.queue1.get(0) # Check contents of message and do what it says # As a test, we simply print it print (msg) self.ui_messages.insert(tkinter.INSERT, "%s\n" % (self.prefix2+msg)) self.ui_messages.yview(tkinter.END) # Auto-scrolling except Queue.Empty: pass # SEND button pressed def sendMsg(self): # Get user input (minus newline character at end) msg = self.ui_input.get("0.0", tkinter.END+"-1c") print("UI: Got text: '%s'" % msg) # Add this data to the message window if msg: self.ui_messages.insert(tkinter.INSERT, "%s\n" % (self.prefix1+msg)) self.ui_messages.yview(tkinter.END) # Auto-scrolling # Clean out input field for new data self.ui_input.delete("0.0", tkinter.END) self.registerEvent(self.queue2,msg) print("Test: Got text: '%s'" % msg) def registerEvent(self,container,msg): container.put(msg) # FILE button pressed def sendFile(self): file = askopenfilename() if(len(file) > 0 and os.path.isfile(file)): print("UI: Selected file: %s" % file) else: print("UI: File operation canceled") self.registerEvent(self.queue3,file) def eventInputClick(self, event): if(self.first_click): # If this is the first time the user clicked, # clear out the tutorial message currently in the box. # Otherwise, ignore it. self.ui_input.delete("0.0", tkinter.END) self.first_click = False;
class GUI: """Graphical user interface to clfit. Data attributes that may sensibly be used externally: exe (string) -- path to clfit executable device (string) -- PGPLOT device, passed to clfit fileName (tkinter.StringVar) -- path to OI-FITS/Mapping Data file (use set() & get() methods) ChangeFileButton (tkinter.Button) -- brings up data file dialog box (may wish to disable) initialdir (string) -- initial directory for file dialog boxes preFitCallback -- function to call just before running clfit. If this returns false, clfit won't be run postFitCallback -- function to call after running clfit (if successful) """ def __init__(self, parent, dismissCommand=None): """Constructor. Arguments are: parent -- parent window dismissCommand -- function to call when Dismiss button clicked """ # Initialise data attributes self.parent = parent self.exe = "clfit" self.device = "/xserv" self.fileName = tk.StringVar() self.fileName.set("(unset)") self.initialdir = os.getcwd() self.preFitCallback = None self.postFitCallback = None self.calErr = tk.StringVar() self.calErr.set("0.0") self.cwl = tk.StringVar() self.bw = tk.StringVar() self.wlmin = tk.StringVar() self.wlmax = tk.StringVar() self.target_id = tk.StringVar() self.nofit = tk.IntVar() self.nofit.set(0) self.plots = [ "No plot", "uv", "vis2", "t3amp", "t3phi", "vis2-wl", "t3amp-wl", "t3phi-wl", "vis2-mjd", "t3amp-mjd", "t3phi-mjd", "vis2-st", "t3amp-st", "t3phi-st", "vis2-pa", "post", "mpost", "post2d", "mpost2d", ] self.selPlot = tk.StringVar() self.selPlot.set(self.plots[1]) self.plotXIndex = tk.StringVar() self.plotXIndex.set("1") self.plotYIndex = tk.StringVar() self.plotYIndex.set("2") self.plotXFrom = tk.StringVar() self.plotXTo = tk.StringVar() self.plotYFrom = tk.StringVar() self.plotYTo = tk.StringVar() self.margErr = tk.IntVar() self.margErr.set(0) self.margErrVar = tk.StringVar() self.margErrVar.set("1") # Initialise GUI elements fileFrame = tk.Frame(parent) fileFrame.pack(side=tk.TOP) tk.Label(fileFrame, text="Data file:").pack(side=tk.LEFT) tk.Label(fileFrame, textvariable=self.fileName).pack(side=tk.LEFT) self.ChangeFileButton = tk.Button( fileFrame, text="Change", command=self._ChangeFileName ) self.ChangeFileButton.pack(side=tk.LEFT) calErrFrame = tk.Frame(parent) calErrFrame.pack(side=tk.TOP, fill=tk.X, pady=4) tk.Label( calErrFrame, text="Calibration Error (extra frac. error in system vis.)" ).pack(side=tk.LEFT, anchor=tk.W) tk.Entry(calErrFrame, textvariable=self.calErr, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) wbFrame = tk.Frame(parent) wbFrame.pack(side=tk.TOP, fill=tk.X, pady=4) tk.Label(wbFrame, text="Waveband:").pack(side=tk.LEFT, anchor=tk.W) tk.Entry(wbFrame, textvariable=self.cwl, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) tk.Entry(wbFrame, textvariable=self.bw, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) tk.Label(wbFrame, text="or Wavelength range:").pack(side=tk.LEFT, anchor=tk.W) tk.Entry(wbFrame, textvariable=self.wlmin, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) tk.Entry(wbFrame, textvariable=self.wlmax, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) targetFrame = tk.Frame(parent) targetFrame.pack(side=tk.TOP, fill=tk.X, pady=4) tk.Label( targetFrame, text="TARGET_ID (blank to use 1st in OI_TARGET table):" ).pack(side=tk.LEFT, anchor=tk.W) tk.Entry(targetFrame, textvariable=self.target_id, width=5).pack( side=tk.LEFT, anchor=tk.W, padx=4 ) tk.Label(parent, text="Model:").pack(side=tk.TOP, anchor=tk.W) self.ModelText = ScrolledText( parent, height=19, width=40, font=("Helvetica", 10) ) self.ModelText.pack(side=tk.TOP, expand=1, fill=tk.BOTH) midFrame1 = tk.Frame(parent) midFrame1.pack(side=tk.TOP, fill=tk.X, pady=4) tk.Label(midFrame1, text="Plot:").pack(side=tk.LEFT, anchor=tk.NW) plotFrame = tk.Frame(midFrame1) plotFrame.pack(side=tk.LEFT) ncol = 3 for i in range(len(self.plots)): p = self.plots[i] tk.Radiobutton(plotFrame, text=p, variable=self.selPlot, value=p).grid( row=int((i + 1) / ncol), column=(i + 1) % ncol, sticky=tk.W ) tk.Entry(plotFrame, textvariable=self.plotXIndex, width=3).grid( row=int(len(self.plots) / ncol) - 1, column=ncol ) tk.Entry(plotFrame, textvariable=self.plotYIndex, width=3).grid( row=int(len(self.plots) / ncol), column=ncol ) rangeFrame = tk.Frame(midFrame1) rangeFrame.pack(side=tk.LEFT) tk.Label(rangeFrame, text="X From:").grid(row=0, column=0, sticky=tk.E) tk.Entry(rangeFrame, textvariable=self.plotXFrom, width=5).grid(row=0, column=1) tk.Label(rangeFrame, text="To:").grid(row=0, column=2) tk.Entry(rangeFrame, textvariable=self.plotXTo, width=5).grid(row=0, column=3) tk.Label(rangeFrame, text="Y From:").grid(row=1, column=0, sticky=tk.E) tk.Entry(rangeFrame, textvariable=self.plotYFrom, width=5).grid(row=1, column=1) tk.Label(rangeFrame, text="To:").grid(row=1, column=2) tk.Entry(rangeFrame, textvariable=self.plotYTo, width=5).grid(row=1, column=3) tk.Label(rangeFrame, text="[Y for (m)post2d only]").grid(row=2, columnspan=4) tk.Button(midFrame1, text="Go", command=self.Go).pack( side=tk.RIGHT, anchor=tk.NE, padx=4 ) tk.Button(midFrame1, text="Save model", command=self.SaveModel).pack( side=tk.RIGHT, anchor=tk.NE, padx=4 ) tk.Button(midFrame1, text="Load model", command=self.LoadModel).pack( side=tk.RIGHT, anchor=tk.NE, padx=4 ) midFrame2 = tk.Frame(parent) midFrame2.pack(side=tk.TOP, fill=tk.X, pady=4) tk.Checkbutton( midFrame2, text="Don't fit (report goodness-of-fit only)", variable=self.nofit, ).pack(side=tk.LEFT, anchor=tk.W, padx=8) tk.Entry(midFrame2, textvariable=self.margErrVar, width=5).pack( side=tk.LEFT, anchor=tk.W ) tk.Checkbutton( midFrame2, text="Error bar by marginalising", variable=self.margErr ).pack(side=tk.LEFT, anchor=tk.W) midFrame3 = tk.Frame(parent) midFrame3.pack(side=tk.TOP, fill=tk.X) tk.Label(midFrame3, text="Results:").pack(side=tk.LEFT, anchor=tk.SW) if dismissCommand is None: dismissCommand = parent.quit tk.Button(midFrame3, text="Dismiss", command=dismissCommand).pack( side=tk.RIGHT, padx=4, pady=4 ) tk.Button(midFrame3, text="Clear results", command=self.ClearResults).pack( side=tk.RIGHT, padx=4, pady=4 ) self.Results = ScrolledText( parent, height=31, width=90, font=("Courier", 10), state=tk.DISABLED ) self.Results.tag_config("result", foreground="#1e90ff") # dodger blue self.Results.tag_config("commentary", foreground="#ff8c00") # dark orange self.Results.tag_config("error", foreground="#8b0000") # dark red self.Results.pack(side=tk.TOP, expand=1, fill=tk.BOTH) def LoadModel(self): """Get filename and read model from file.""" fileName = tk_filedialog.askopenfilename( parent=self.parent, initialdir=self.initialdir, filetypes=[("mfit model files", "*.model"), ("All files", "*")], ) if fileName != "": self.ReadModel(fileName) def ReadModel(self, fileName): """Read model from file.""" try: fil = open(fileName, "r") text = fil.read() fil.close() except IOError as e: (errNo, errStr) = e.args self.ShowResult("Error reading %s: %s\n" % (fileName, errStr), "error") else: self.SetModel(text) def SetModel(self, text): """Set model text.""" self.ModelText.delete(1.0, tk.END) self.ModelText.insert(tk.END, text) def ClearResults(self): """Clear results window.""" self.Results.configure(state=tk.NORMAL) self.Results.delete(1.0, tk.END) self.Results.configure(state=tk.DISABLED) def SaveModel(self): """Get filename and write model to file.""" fileName = tk_filedialog.asksaveasfilename( parent=self.parent, initialdir=self.initialdir, filetypes=[("mfit model files", "*.model"), ("All files", "*")], ) if fileName != "": self.WriteModel(fileName) def WriteModel(self, fileName): """Write model text to file.""" fil = open(fileName, "w") fil.write(self.ModelText.get(1.0, tk.END)) fil.close() def _ChangeFileName(self): newName = tk.filedialog.askopenfilename( parent=self.parent, initialdir=self.initialdir, title="Choose data file for fit", filetypes=[ ("(OI-)FITS files", "*fits"), ("COAST Mapping Data files", "*.mapdat"), ("wbCalib / nbCalib files", "*calib"), ("All files", "*"), ], ) if newName != "": self.fileName.set(newName) def ShowResult(self, text, tag): """Display text in 'Results'.""" self.Results.configure(state=tk.NORMAL) self.Results.insert(tk.END, text, tag) self.Results.yview(tk.END) self.Results.configure(state=tk.DISABLED) self.parent.update_idletasks() def Go(self): """Fit the current model.""" # Execute pre-callback if callable(self.preFitCallback): if not self.preFitCallback(): return # Write model text to tempfile self._tempName = tempfile.mktemp(".model") self.WriteModel(self._tempName) # Run clfit so we can grab its output args = ["nice", self.exe, "--device", self.device] try: c = float(self.calErr.get()) except ValueError: pass else: args += f"--calerr {c:.3f}".split() try: cwl = float(self.cwl.get()) bw = float(self.bw.get()) except ValueError: try: wlmin = float(self.wlmin.get()) wlmax = float(self.wlmax.get()) args += f"--waverange {wlmin:.2f} {wlmax:.2f}".split() except ValueError: pass # don't specify waveband(s) else: args += f"--waveband {cwl:.2f} {bw:.2f}".split() try: target_id = int(self.target_id.get()) args += f"--target_id {target_id}".split() except ValueError: pass p = self.selPlot.get() if p != self.plots[0]: # not 'No plot' if p == "post" or p == "mpost": try: index = int(self.plotXIndex.get()) except ValueError: index = 1 if p == "post2d" or p == "mpost2d": try: indx = (int(self.plotXIndex.get()), int(self.plotYIndex.get())) except ValueError: indx = (1, 2) try: xmin = float(self.plotXFrom.get()) xmax = float(self.plotXTo.get()) if p[-2:] == "2d": ymin = float(self.plotYFrom.get()) ymax = float(self.plotYTo.get()) except ValueError: if p == "post" or p == "mpost": args += f"--plot {p} {index}".split() elif p == "post2d" or p == "mpost2d": args += f"--plot {p} {indx[0]} {indx[1]}".split() else: args += f"--plot {p}".split() else: if p == "post" or p == "mpost": args += f"--zoomplot {p} {index} {xmin} {xmax}".split() elif p == "post2d" or p == "mpost2d": args += ( f"--zoomplot {p} {indx[0]} {indx[1]} {xmin:.4f} {xmax:.4f}" f" {ymin:.4f} {ymax:.4f}".split() ) else: args += f"--zoomplot {p} {xmin:.4f} {xmax:.4f}".split() if self.nofit.get(): args += ["--nofit"] if self.margErr.get(): args += f"--margerr {self.margErrVar.get()}".split() args += [self.fileName.get(), self._tempName] self.ShowResult("Running %s:\n" % " ".join(args), tag="commentary") # https://gitpress.io/u/1282/tkinter-read-async-subprocess-output self._proc = Popen(args, bufsize=10, stdout=PIPE, stderr=STDOUT, close_fds=True) oldflags = fcntl.fcntl(self._proc.stdout, fcntl.F_GETFL) fcntl.fcntl(self._proc.stdout, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) self.parent.createfilehandler( self._proc.stdout, tk.READABLE, self._HandleChildOutput ) def _HandleChildOutput(self, fd, mask): """Handle output from child process.""" self._ShowOutput() returncode = self._proc.poll() if returncode is not None: # child process completed time.sleep(0.5) # wait for last output self._ShowOutput() if returncode != 0: self.ShowResult(f"Subprocess exited with code {returncode}\n", "error") else: self.ShowResult("Subprocess exited normally\n", "commentary") self.parent.deletefilehandler(self._proc.stdout) os.remove(self._tempName) # Execute post-callback if callable(self.postFitCallback): self.postFitCallback() def _ShowOutput(self): """Read child process output and pass to ShowResult().""" try: result = self._proc.stdout.read() except IOError as e: (errNo, errMsg) = e.args self.ShowResult("I/O Error %d: %s\n" % (errNo, errMsg), "commentary") else: self.ShowResult(result, "result")
class clientUI(): def __init__(self): self.first_click = True; def start(self): print("Starting clientUI...") self.initDisplay() self.ui_messages.insert(tkinter.END, "Adding a message to the text field...\n") self.ui_input.insert(tkinter.END, "<Enter message>") # This call to mainloop() is blocking and will last for the lifetime # of the GUI. self.ui_top.mainloop() # Should only get here after destroy() is called on ui_top print("Stopping clientUI...") def initDisplay(self): self.ui_top = tkinter.Tk() self.ui_top.wm_title("GUI Demo") self.ui_top.resizable('1','1') self.ui_top.protocol("WM_DELETE_WINDOW", self.eventDeleteDisplay) self.ui_messages = ScrolledText( master=self.ui_top, wrap=tkinter.WORD, width=50, # In chars height=25) # In chars self.ui_input = tkinter.Text( master=self.ui_top, wrap=tkinter.WORD, width=50, height=4) # Bind the button-1 click of the Entry to the handler self.ui_input.bind('<Button-1>', self.eventInputClick) self.ui_button_send = tkinter.Button( master=self.ui_top, text="Send", command=self.sendMsg) self.ui_button_file = tkinter.Button( master=self.ui_top, text="File", command=self.sendFile) # Compute display position for all objects self.ui_messages.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_input.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_button_send.pack(side=tkinter.LEFT) self.ui_button_file.pack(side=tkinter.RIGHT) # SEND button pressed def sendMsg(self): # Get user input (minus newline character at end) msg = self.ui_input.get("0.0", tkinter.END+"-1c") print("UI: Got text: '%s'" % msg) # Add this data to the message window self.ui_messages.insert(tkinter.INSERT, "%s\n" % (msg)) self.ui_messages.yview(tkinter.END) # Auto-scrolling # Clean out input field for new data self.ui_input.delete("0.0", tkinter.END) # FILE button pressed def sendFile(self): file = askopenfilename() if(len(file) > 0 and os.path.isfile(file)): print("UI: Selected file: %s" % file) else: print("UI: File operation canceled") # Event handler - User closed program via window manager or CTRL-C def eventDeleteDisplay(self): print("UI: Closing") # Continuing closing window now self.ui_top.destroy() # Event handler - User clicked inside the "ui_input" field def eventInputClick(self, event): if(self.first_click): # If this is the first time the user clicked, # clear out the tutorial message currently in the box. # Otherwise, ignore it. self.ui_input.delete("0.0", tkinter.END) self.first_click = False;
class PfBGUI: """ the GUI for selecting pixel centers from image as a set of points """ hypdatadir = "" # location of hyperspectral data, initial suggestion in file dialog def __init__(self, master, openfilelist=None, exportpointlist=None): """ signalPfB: variable to signal the caller that we are finished, type DoubleVar() openfilelist: list of the loaded hyperspectral file name and handles: [ filename filehandle datahandle ] intially, when loading the hyperspectral handles are set to None; they are assigned when file is opened for e.g. plotting exportpointlist: output list of tuples (id,x,y), where x,y are point (pixel centre) coordinates in the global projected system the pointlist created here is appended to exportpointlist if called with an exportpointlist, upon exit emits the signal master.<<PfBGUI_exit>> """ self.master = master # the master is likely tkroot self.openfilelist = openfilelist self.exportpointlist = exportpointlist # Show the GUI in a Toplevel window instead of Root. # This allows many such programs to be run independently in parallel. self.w = Toplevel(master) self.w.title("GUI for selecting pixels based on band values") self.bandnames = [] # list of band names as strings self.coordlist = [ [], [] ] # list of coordinate points as produced by np.nonzero(), i.e., list of two lists, containing x and y coordinates, respectively # NOTE: these are image coordinates (line,pixel), i.e., (y,x) # will be upended to exportpointlist on exit self.DIV = StringVar() # Data Ignore Value self.DIV.set("") self.fig_hypdata = None # figure handle for the image of th selected band self.xlim = [ ] # plotting limits for fig_hypdata: these will be retained during band and mask changes self.ylim = [] self.figmask = None # handle for the mask plt.imshow object self.band = StringVar( ) # tkinter string to set and get the value in band optionmenu self.textlog = ScrolledText(self.w, height=6) #just in case, put everythin in a frame, stuff can be added later if needed self.frame_buttons = Frame(self.w) bw = 35 # button width self.button_quit = Button(self.frame_buttons, text='´Done', width=bw, command=self.buttondone) self.button_datafile = Button(self.frame_buttons, text='Load datafile', width=bw, command=self.datafile) self.button_savepoints = Button(self.frame_buttons, text="save points in file", width=bw, command=self.savepoints) self.combo_band = ttk.Combobox(self.frame_buttons, textvariable=self.band, values=["select band"]) self.combo_band.bind("<<ComboboxSelected>>", self.selectband) self.combo_band['width'] = bw - 5 self.combo_band['state'] = DISABLED self.button_plotband = Button(self.frame_buttons, text='Plot band', width=bw, command=self.plotband, state=DISABLED) self.label_min = Label(self.frame_buttons, width=bw, text="Lower value:") self.minvaluestring = StringVar() self.minvaluestring.set("-") self.entry_minvalue = Entry(self.frame_buttons, textvariable=self.minvaluestring) self.label_max = Label(self.frame_buttons, width=bw, text="Upper value:") self.maxvaluestring = StringVar() self.maxvaluestring.set("-") self.entry_maxvalue = Entry(self.frame_buttons, textvariable=self.maxvaluestring) self.button_applyrange = Button(self.frame_buttons, text='Pick points', width=bw, command=self.applyrange, state=DISABLED) self.label_DIV = Label(self.frame_buttons, width=bw, text="Data Ignore Value:") self.entry_DIV = Entry(self.frame_buttons, width=bw, textvariable=self.DIV) self.label_id = Label(self.frame_buttons, width=bw, text="ID string for points:") self.point_id = StringVar() self.point_id.set("THRSHLD") self.entry_id = Entry(self.frame_buttons, width=bw, textvariable=self.point_id) self.label_N = Label(self.frame_buttons, width=bw, text="Points: 0") self.textlog.pack(side='bottom') if self.openfilelist is None: self.button_datafile.pack(side='top') else: # load the data file directly # assume that at least file name is given if self.openfilelist[1] is None or self.openfilelist[2] is None: # the data files are not opened yet self.load_hypdata() self.fill_bandnames() self.combo_band.pack(side='top') self.button_plotband.pack(side='top') self.label_min.pack(side='top') self.entry_minvalue.pack(side='top') self.label_max.pack(side='top') self.entry_maxvalue.pack(side='top') self.button_applyrange.pack(side='top') self.button_savepoints.pack(side='top') self.label_N.pack(side='top') self.label_DIV.pack(side='top') self.entry_DIV.pack(side='top') self.label_id.pack(side='top') self.entry_id.pack(side='top') self.button_quit.pack(side='bottom') self.frame_buttons.pack(side='left') def datafile(self): """ get data file name and load metadata """ filename1 = filedialog.askopenfilename( initialdir=self.hypdatadir, title="Choose hyperspectal data file", filetypes=(("ENVI header files", "*.hdr"), ("all files", "*.*"))) if filename1 != "": self.openfilelist = [filename1, None, None] self.load_hypdata() self.fill_bandnames() shortfilename = os.path.split(filename1)[1] shortfilename = os.path.splitext(shortfilename)[0] self.button_datafile.configure(text="File: " + shortfilename) def load_hypdata(self): """ load the file handles into openfilelist and check for wavelength data """ # open the data file -- reads only metadata hypdata = spectral.open_image(self.openfilelist[0]) # hypdata.metadata is of type dict, use e.g. hypdata.metadata.keys() # print(hypdata.metadata.keys()) if hypdata.interleave == 1: self.printlog(self.openfilelist[0] + " Band interleaved (BIL) \n") else: self.printlog( self.openfilelist[0] + " not BIL -- opening still as BIL -- will be slower \n") hypdata_map = hypdata.open_memmap() self.printlog( "data file dimensions " + ", ".join([str(i) for i in hypdata_map.shape]) + "\n") #shape[0]==lines, shape[1]==pixels, shape[2]==bands # save the handles to the openfilelist self.openfilelist = [self.openfilelist[0], hypdata, hypdata_map] def fill_bandnames(self): """ Retrieve band names from the datafile metadata and fill the OptionMenu Sets self.DIV Entry (Data Ignore Value) """ hypfilename = self.openfilelist[0] hypdata = self.openfilelist[1] self.bandnames = [] self.wl_hyp, wl_found = get_wavelength(hypfilename, hypdata) if 'band names' in hypdata.metadata: self.bandnames = hypdata.metadata['band names'] # add wavelength as integer for each band name self.bandnames = [ str(int(i)) + " " + j for i, j in zip(self.wl_hyp, self.bandnames) ] else: # name as wavelength: even if not given, negative integers are in wl_hyp self.bandnames = [str(int(i)) for i in self.wl_hyp] DIV = get_DIV(hypfilename, hypdata) if DIV is not None: self.DIV.set(str(DIV)) self.entry_DIV.delete(0, END) self.entry_DIV.insert(0, str(DIV)) # fill the band name ComboBox self.combo_band['state'] = ACTIVE self.combo_band.configure(values=self.bandnames) self.combo_band.delete(0, END) self.combo_band.insert(0, self.bandnames[0]) self.selectband() self.button_plotband.configure(state=ACTIVE) self.button_applyrange.configure(state=ACTIVE) def selectband(self, event=None): """ Activate a selection in the band selection ComboBox and load pixel value ranges """ choice = self.combo_band.get() bandnumber = self.bandnames.index(choice) bandimage = self.openfilelist[2][:, :, bandnumber] if self.DIV.get() != "": DIV = float(self.DIV.get()) else: DIV = None i_data = np.nonzero(bandimage != DIV) minvalue = np.min(bandimage[i_data]) maxvalue = np.max(bandimage[i_data]) self.entry_minvalue.delete(0, END) # to avoid digitization errors, we must always know if the field contains the actual min and max self.entry_minvalue.insert( 0, "min=" + str(minvalue) ) #prefix with min to indicate that we have the histogram minimum self.entry_maxvalue.delete(0, END) self.entry_maxvalue.insert(0, "max=" + str(maxvalue)) def plotband(self): """ plot the band using imshow """ bandnumber = self.bandnames.index(self.band.get()) self.printlog("Plotting band " + self.band.get() + " [" + str(bandnumber) + "]\n") hypfilename = self.openfilelist[0] hypdata = self.openfilelist[1] hypdata_map = self.openfilelist[2] self.xlim = None self.ylim = None if self.fig_hypdata is not None: if self.fig_hypdata.number in plt.get_fignums(): # save the current view if the window exists self.xlim = self.fig_hypdata.axes[0].get_xlim() self.ylim = self.fig_hypdata.axes[0].get_ylim() if self.figmask is not None: # remove the old mask self.figmask.remove() self.figmask = None self.fig_hypdata = plot_singleband(hypfilename, hypdata, hypdata_map, bandnumber, fig_hypdata=self.fig_hypdata, outputcommand=self.printlog) if self.xlim is not None: # restore the previous view self.fig_hypdata.axes[0].set_xlim(self.xlim) self.fig_hypdata.axes[0].set_ylim(self.ylim) self.fig_hypdata.canvas.draw() def savepoints(self): """ save the created points (i.e., their coordinates, not spectra) to a text file. """ if len(self.coordlist[0]) > 0: filename = filedialog.asksaveasfilename( initialdir=self.hypdatadir, title="Save point coordinates (X,Y)", filetypes=(("txt files", "*.txt"), ("csv files", "*.csv"), ("all files", "*.*"))) if filename != '': with open(filename, 'w') as file: file.write("id,x,y\n") pointlist = self.get_pointlist() for point in pointlist: pointstring = point[0] + "," + str( point[1]) + ',' + str(point[2]) + '\n' file.write(pointstring) self.printlog("Point coordinates saved to " + filename + ".\n") else: self.printlog("Saving of point coordinates aborted.\n") else: self.printlog("savepoints(): No points, nothing saved.\n") def applyrange(self): """ Create the points """ hypfilename = self.openfilelist[0] hypdata_map = self.openfilelist[2] bandnumber = self.bandnames.index(self.band.get()) bandimage = hypdata_map[:, :, bandnumber] if self.DIV.get() != "": DIV = float(self.DIV.get()) else: DIV = None i_data = np.nonzero(bandimage != DIV) if self.minvaluestring.get()[0] == "m": # the entry field contains unmodified histogram minimum minvalue = np.min(bandimage[i_data]) else: minvalue = float(self.minvaluestring.get()) if self.maxvaluestring.get()[0] == "m": # the entry field contains unmodified histogram maximum maxvalue = np.max(bandimage[i_data]) else: maxvalue = float(self.maxvaluestring.get()) self.coordlist = np.nonzero( np.logical_and( np.logical_and(bandimage >= minvalue, bandimage <= maxvalue), bandimage != DIV)) N = len(self.coordlist[0]) self.label_N.configure(text="Points: " + str(N)) # plot the mask only if the band image exists if self.fig_hypdata is not None: if self.fig_hypdata.number in plt.get_fignums(): # just in case: save the current view self.xlim = self.fig_hypdata.axes[0].get_xlim() self.ylim = self.fig_hypdata.axes[0].get_ylim() mask = np.zeros_like( bandimage, dtype=float) # NaN's don't work with integers mask[self.coordlist] = np.NaN # NaN plots as transparent if self.figmask is not None: # remove the old mask self.figmask.remove() self.figmask = self.fig_hypdata.axes[0].imshow( mask, interpolation='nearest') # restore the previous view self.fig_hypdata.axes[0].set_xlim(self.xlim) self.fig_hypdata.axes[0].set_ylim(self.ylim) self.fig_hypdata.canvas.draw() def buttondone(self): """ function to end the misery and return the points """ if self.fig_hypdata is not None: plt.close(self.fig_hypdata) if self.exportpointlist is not None and len(self.coordlist[0]) > 0: pointlist = self.get_pointlist() self.exportpointlist += pointlist self.master.event_generate("<<PfBGUI_exit>>", when="tail") self.w.destroy( ) # if we were called to create points, don't destroy root, computation will continue elsewhere else: self.master.destroy( ) # destruction of root will exit the main loop and allow the program to exit def get_pointlist(self): """ outputs pointlist, a list of tuples (id,x,y) using ID and self.coordlist (y_loc, x_loc) x,y are in global coordinates """ id = self.point_id.get() IDlist = [id] * len(self.coordlist[0]) pointmatrix_local = np.matrix( self.coordlist).transpose()[:, (1, 0)] # swap x & y pointmatrix_global = image2world(self.openfilelist[0], pointmatrix_local) pointlist = [(id, Q[0], Q[1]) for id, Q in zip(IDlist, pointmatrix_global.tolist())] return pointlist def printlog(self, text): """ Output to log window. Note: no newline added beteen inputs. text need not be a string, will be converted when printing. """ self.textlog.insert(END, str(text)) self.textlog.yview(END) self.master.update_idletasks()
class App(Frame): lock = False def __init__(self, master=None): Frame.__init__(self, master) # self. grid() self.pack() self.master = master self.download_type = IntVar() self.audio_only = BooleanVar() self.from_url = StringVar() self.to_url = StringVar() self.radois = Frame(self, relief=RAISED, borderwidth=1) self.radois.pack(fill=BOTH, expand=True) self.download_playlist = Radiobutton(self.radois, text='Плейлист целиком', variable=self.download_type, value=0) self.download_playlist.pack(ipadx=10, ipady=10, side=LEFT) self.download_file_only = Radiobutton(self.radois, text='Только файл', variable=self.download_type, value=1) self.download_file_only.pack(ipadx=10, ipady=10, side=LEFT) self.download_url_label = Label(text="Откуда качать") self.download_url_label.pack(ipadx=10, ipady=10) self.download_url = Entry(self.master, textvariable=self.from_url) self.download_url.pack(ipadx=10, ipady=3, expand=True, fill=BOTH) self.download_url_label = Label(text="Куда качать") self.download_url_label.pack(ipadx=10, ipady=10) self.save_url = Entry(self.master, textvariable=self.to_url) self.save_url.pack(ipadx=10, ipady=3, expand=True, fill=BOTH) self.audio_checkbox = Checkbutton(self.master, text='Только аудио', variable=self.audio_only, onvalue=True, offvalue=False) self.audio_checkbox.pack(ipadx=10, ipady=10) self.message_button = Button(text="Скачать", command=self.run_program) self.message_button.pack(ipadx=10, ipady=10) self.editArea = ScrolledText(master=self.master, wrap=WORD, width=20, height=10) self.editArea.pack(padx=10, pady=10, fill=BOTH, expand=True) def run_program(self): if not self.lock: self.lock = True thread = threading.Thread( target=self.run, args=(), ) thread.daemon = True thread.start() else: messagebox.showerror( "Error", "В данный момент уже идет загрузка, подождите окончания предыдущей и попробуйте снова!" ) def run(self): if self.download_type.get() == 0: self.edit_print("Качаем весь плейлист") download_playlist(self.from_url.get(), self.to_url.get(), self.audio_only.get()) elif self.download_type == 1: self.edit_print("Качаем один файл") download_song(self.from_url.get(), self.to_url.get(), self.audio_only.get()) self.lock = False def edit_print(self, text): self.editArea.insert(END, text + "\n") self.editArea.yview(END)
class clientUI(): def __init__(self, a_socket, a_username): self.first_click = True; self.a_socket = a_socket self.a_username = a_username def start(self): print("Starting clientUI...") self.initDisplay() self.ui_messages.insert(tkinter.END, "Adding a message to the text field...\n") self.ui_input.insert(tkinter.END, "<Enter message>") def execute(self): self.ui_top.mainloop() # This call to mainloop() is blocking and will last for the lifetime of the GUI. print("Stopping clientUI...") # Should only get here after destroy() is called on ui_top def initDisplay(self): self.ui_top = tkinter.Tk() self.ui_top.wm_title("GUI Demo") self.ui_top.resizable('1','1') self.ui_top.protocol("WM_DELETE_WINDOW", self.eventDeleteDisplay) self.ui_messages = ScrolledText( master=self.ui_top, wrap=tkinter.WORD, width=50, # In chars height=25) # In chars self.ui_input = tkinter.Text( master=self.ui_top, wrap=tkinter.WORD, width=50, height=4) # Bind the button-1 click of the Entry to the handler self.ui_input.bind('<Button-1>', self.eventInputClick) self.ui_button_send = tkinter.Button( master=self.ui_top, text="Send", command=self.sendMsg) self.ui_button_file = tkinter.Button( master=self.ui_top, text="File", command=self.sendFile) # Compute display position for all objects self.ui_messages.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_input.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_button_send.pack(side=tkinter.LEFT) self.ui_button_file.pack(side=tkinter.RIGHT) # SEND button pressed def sendMsg(self): # Get user input (minus newline character at end) msg = self.a_username + ": " + self.ui_input.get("0.0", tkinter.END+"-1c") msg_len = len(msg) print("UI: Got text: '%s'" % msg) print("Starting send thread . . .") thread1 = sndThread(self.a_socket, self.a_username, msg_len, msg) #Launch sending Thread ############################################################### thread1.daemon = True thread1.start() self.ui_messages.insert(tkinter.INSERT, "%s\n" % (msg)) # Add this data to the message window self.ui_messages.yview(tkinter.END) # Auto-scrolling # Clean out input field for new data self.ui_input.delete("0.0", tkinter.END) # FILE button pressed def sendFile(self): file = askopenfilename() if(len(file) > 0 and os.path.isfile(file)): print("UI: Selected file: %s" % file) else: print("UI: File operation canceled") # Event handler - User closed program via window manager or CTRL-C def eventDeleteDisplay(self): leave_msg = "CHAT/1.0 LEAVE\r\nUsername: "******"\r\n\r\n" try: #SEND leave_raw_bytes = bytes(leave_msg,'ascii') leave_bytes_sent = self.a_socket.sendall(leave_raw_bytes) except socket.error as msg: print("Error: send() failed\nDescription: " + str(msg)) sys.exit() try: #CLOSE SOCKET self.a_socket.close() except socket.error as msg: print("Error: unable to close() socket\nDescription: " + str(msg)) sys.exit() print("Socket closed, now exiting") print("UI: Closing") self.ui_top.destroy() # Continuing closing window now # Event handler - User clicked inside the "ui_input" field def eventInputClick(self, event): if(self.first_click): # If this is the first time the user clicked, clear out the tutorial message currently in the box. Otherwise, ignore it. self.ui_input.delete("0.0", tkinter.END) self.first_click = False;
class clientUI(): def __init__(self): self.first_click = True def start(self): print("Запускаем чат...") self.initDisplay() self.ui_messages.insert(tkinter.END, "Добавление сообщения в текстовое поле...\n") self.ui_input.insert(tkinter.END, "<Отправить сообщение>") self.ui_top.mainloop() print("Останавливаем чат...") def initDisplay(self): self.ui_top = tkinter.Tk() self.ui_top.wm_title("Это чат") self.ui_top.resizable('1', '1') self.ui_top.protocol("WM_DELETE_WINDOW", self.eventDeleteDisplay) self.ui_messages = ScrolledText(master=self.ui_top, wrap=tkinter.WORD, width=50, height=25) self.ui_input = tkinter.Text(master=self.ui_top, wrap=tkinter.WORD, width=50, height=4) # Привязать <Кнопка-1> клика Entry к обработчику self.ui_input.bind('<Кнопка-1>', self.eventInputClick) self.ui_button_send = tkinter.Button(master=self.ui_top, text="Отправить", command=self.sendMsg) self.ui_button_file = tkinter.Button(master=self.ui_top, text="Файл", command=self.sendFile) # Вычисляем положение дисплея для всех объектов self.ui_messages.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_input.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_button_send.pack(side=tkinter.LEFT) self.ui_button_file.pack(side=tkinter.RIGHT) # Кнопка ОТПРАВИТЬ def sendMsg(self): msg = self.ui_input.get("0.0", tkinter.END + "-1c") print("Чат: Получил сообщение: '%s'" % msg) self.ui_messages.insert(tkinter.INSERT, "%s\n" % (msg)) self.ui_messages.yview(tkinter.END) self.ui_input.delete("0.0", tkinter.END) # Кнопка ФАЙЛ def sendFile(self): file = askopenfilename() if (len(file) > 0 and os.path.isfile(file)): print("Чат: Выбран файл: %s" % file) else: print("Чат: Отмена") # Закрытие Чата def eventDeleteDisplay(self): print("Чат: Закрытие") self.ui_top.destroy() # Обработчик события def eventInputClick(self, event): if (self.first_click): self.ui_input.delete("0.0", tkinter.END) self.first_click = False
class disp_GUI: def __init__(self, master): self.master = master self.fig_hypdata = None self.hypdata = None # This contains only the mos recently opened figure. Rather useless self.hypdata_map = None bw = 25 # buttonwidth # Show the GUI in a Toplevel window instead of Root. # This allows many such programs to be run independently in parallel. self.w = Toplevel(master) self.w.title("GUI for plotting data") self.redband_string = StringVar( ) # string to set and read option_redband OptionMenu self.greenband_string = StringVar( ) # string to set and read option_greenband OptionMenu self.blueband_string = StringVar( ) # string to set and read option_blueband OptionMenu self.monoband_string = StringVar( ) # string to set and read option_monoband OptionMenu self.redband_string.set("Red band") self.redband_string.set("Green band") self.redband_string.set("Blue band") self.redband_string.set("Monochrome band") self.textlog = ScrolledText(self.w, height=6) self.textlog.pack(side='bottom') self.frame_rgb = Frame(self.w) self.label_red = Label(self.frame_rgb, width=bw, text="Red channel") self.label_green = Label(self.frame_rgb, width=bw, text="Green channel") self.label_blue = Label(self.frame_rgb, width=bw, text="Blue channel") self.label_mono = Label(self.frame_rgb, width=bw, text="Monochrome channel") self.option_red = OptionMenu(self.frame_rgb, self.redband_string, '') self.option_red['width'] = bw - 5 self.option_green = OptionMenu(self.frame_rgb, self.greenband_string, '') self.option_green['width'] = bw - 5 self.option_blue = OptionMenu(self.frame_rgb, self.blueband_string, '') self.option_blue['width'] = bw - 5 self.option_mono = OptionMenu(self.frame_rgb, self.monoband_string, '') self.option_mono['width'] = bw - 5 self.label_red.pack(side='top') self.option_red.pack(side='top') self.option_red.configure(state=DISABLED) self.label_green.pack(side='top') self.option_green.pack(side='top') self.option_green.configure(state=DISABLED) self.label_blue.pack(side='top') self.option_blue.pack(side='top') self.option_blue.configure(state=DISABLED) self.label_mono.pack(side='top') self.option_mono.pack(side='top') self.option_mono.configure(state=DISABLED) self.frame_rgb.pack(side='right') self.frame_button = Frame(self.w) self.button_quit = Button(self.frame_button, width=bw, text='Quit', command=self.buttonquit) self.button_loaddata = Button(self.frame_button, width=bw, text='Load raster file', command=self.loaddatafile) self.button_plottrue = Button(self.frame_button, width=bw, text='Plot truecolor', command=self.plottrue, state=DISABLED) self.button_plotmono = Button(self.frame_button, width=bw, text='Plot monochrome', command=self.plotmono, state=DISABLED) self.button_plotnir = Button(self.frame_button, width=bw, text='Plot falsecolor NIR', command=self.plotnir, state=DISABLED) self.button_plotrgb = Button(self.frame_button, width=bw, text='Plot with three bands', command=self.plotrgb, state=DISABLED) self.button_loaddata.pack(side='top') self.button_plottrue.pack(side='top') self.button_plotnir.pack(side='top') self.button_plotrgb.pack(side='top') self.button_plotmono.pack(side='top') self.button_quit.pack(side='bottom') self.frame_button.pack(side='left') # load paths at the end of init (so messaging already exists) # self.foldername1 = 'D:\\mmattim\\wrk\\hyytiala-D\\' # where the data is. This is the initial value, will be modified later self.foldername1 = get_hyperspectral_datafolder( localprintcommand=self.printlog) def loaddatafile(self): """ Open the hyperspectral file. """ self.hypfilename = filedialog.askopenfilename( initialdir=self.foldername1, title="Choose a hyperspectral data file", filetypes=(("Envi hdr files", "*.hdr"), ("all files", "*.*"))) if self.hypfilename != '': self.foldername1 = os.path.split(self.hypfilename)[0] self.hypdata_map = None self.hypdata = None self.button_plotmono.configure(state=DISABLED) self.button_plotrgb.configure(state=DISABLED) self.button_plotnir.configure(state=DISABLED) self.button_plottrue.configure(state=DISABLED) # try to have .hdr extension, although this should not be compulsory. No error checking here. if not self.hypfilename.endswith(".hdr"): self.hypfilename += '.hdr' # open the files and assign handles self.hypdata = spectral.open_image(self.hypfilename) self.hypdata_map = self.hypdata.open_memmap() # come up with band names wl_hyp, wl_found = get_wavelength(self.hypfilename, self.hypdata) # best possible result "number:wavelength" bandnames = [ '%3d :%6.1f nm' % (i + 1, wli) for i, wli in enumerate(wl_hyp) ] if wl_found: self.printlog( "loaddatafile(): Found wavelength information in file " + self.hypfilename + ".\n") else: self.printlog( "loaddatafile(): No wavelength information in file " + self.hypfilename + ".\n") # try to use band name information if 'band names' in self.hypdata.metadata: bn = self.hypdata.metadata['band names'] bandnames = [ '%3d:%s' % (i + 1, wli) for i, wli in enumerate(bn) ] # fill the option menus with wavelengths self.option_red['menu'].delete(0, END) self.option_green['menu'].delete(0, END) self.option_blue['menu'].delete(0, END) self.option_mono['menu'].delete(0, END) for choice_num in bandnames: choice = str(choice_num) self.option_red['menu'].add_command( label=choice, command=lambda v=choice: self.redband_string.set(v)) self.option_green['menu'].add_command( label=choice, command=lambda v=choice: self.greenband_string.set(v)) self.option_blue['menu'].add_command( label=choice, command=lambda v=choice: self.blueband_string.set(v)) self.option_mono['menu'].add_command( label=choice, command=lambda v=choice: self.monoband_string.set(v)) # make reasonable preselections for r,g,b if 'default bands' in self.hypdata.metadata: if len(self.hypdata.metadata['default bands']) > 2: i_r = int(self.hypdata.metadata['default bands'][0]) - 1 i_g = int(self.hypdata.metadata['default bands'][1]) - 1 i_b = int(self.hypdata.metadata['default bands'][2]) - 1 # avoid official printing band names, they usually contain long crappy strings self.printlog( "loaddatafile(): Found default bands (%i,%i,%i) for plotting.\n" % (i_r, i_g, i_b)) else: i_m = int(self.hypdata.metadata['default bands'][0]) - 1 i_r = i_m i_g = i_m i_b = i_m # avoid official printing band names, they usually contain long crappy strings self.printlog( "loaddatafile(): Found one default band (%i) for plotting.\n" % i_m) elif wl_found: i_r = abs(wl_hyp - 680).argmin() # red band i_g = abs(wl_hyp - 550).argmin() # green i_b = abs(wl_hyp - 450).argmin() # blue else: # just use the first one or three bands if self.hypdata_map.shape[2] > 2: # we have at least 3 bands i_r = 0 i_g = 1 i_b = 2 else: # monochromatic, use first band only i_r = 0 i_g = 0 i_b = 0 # set monochrome to red i_m = i_r # set the optionmenus to their respective values self.redband_string.set(bandnames[i_r]) self.greenband_string.set(bandnames[i_g]) self.blueband_string.set(bandnames[i_b]) self.monoband_string.set(bandnames[i_m]) # wrap it up. Make sure all options are active and ready self.option_red.configure(state=ACTIVE) self.option_green.configure(state=ACTIVE) self.option_blue.configure(state=ACTIVE) self.option_mono.configure(state=ACTIVE) self.button_plotmono.configure(state=ACTIVE) self.button_plotrgb.configure(state=ACTIVE) if wl_found: self.button_plotnir.configure(state=ACTIVE) self.button_plottrue.configure(state=ACTIVE) else: self.printlog("loaddatafile(): No file name given.\n") def plotrgb(self): """ plot in true color, ignore r,g,b band optionmenus """ i_r = int(self.redband_string.get().split(':')[0]) - 1 i_g = int(self.greenband_string.get().split(':')[0]) - 1 i_b = int(self.blueband_string.get().split(':')[0]) - 1 self.printlog("plotrgb(): bands %3d,%3d,%3d.\n" % (i_r, i_g, i_b)) self.fig_hypdata = plot_hyperspectral(self.hypfilename, self.hypdata, self.hypdata_map, self.printlog, plotbands=[i_r, i_g, i_b]) def plottrue(self): """ plot using the r,g,b band optionmenus """ wl_hyp, wl_found = get_wavelength(self.hypfilename, self.hypdata) if wl_found: i_r = abs(wl_hyp - 680).argmin() # red band i_g = abs(wl_hyp - 550).argmin() # green i_b = abs(wl_hyp - 450).argmin() # blue self.printlog("plottrue(): %5.1f,%5.1f,%5.1f nm.\n" % (wl_hyp[i_r], wl_hyp[i_g], wl_hyp[i_b])) self.fig_hypdata = plot_hyperspectral(self.hypfilename, self.hypdata, self.hypdata_map, self.printlog, plotbands=[i_r, i_g, i_b]) else: # this should never happen, but just in case self.printlog( "plottrue(): No wavelength data available. This should never happen.\n" ) def plotnir(self): """ plot in falsecolor NIR, ignore r,g,b band optionmenus """ wl_hyp, wl_found = get_wavelength(self.hypfilename, self.hypdata) if wl_found: i_r = abs(wl_hyp - 780).argmin() # NIR band i_g = abs(wl_hyp - 680).argmin() # red band i_b = abs(wl_hyp - 550).argmin() # green self.printlog("plotnir(): %5.1f,%5.1f,%5.1f nm.\n" % (wl_hyp[i_r], wl_hyp[i_g], wl_hyp[i_b])) self.fig_hypdata = plot_hyperspectral(self.hypfilename, self.hypdata, self.hypdata_map, self.printlog, plotbands=[i_r, i_g, i_b]) else: # this should never happen, but just in case self.printlog( "plotnir(): No wavelength data available. This should never happen.\n" ) def plotmono(self): """ Plot the data with the band specified in the monochrome option menu """ i_m = int(self.monoband_string.get().split(':')[0]) - 1 plot_singleband(self.hypfilename, self.hypdata, self.hypdata_map, i_m, outputcommand=self.printlog) def printlog(self, text): """ Output to log window. Note: no newline added beteen inputs. text need not be a string, will be converted when printing. """ self.textlog.insert(END, str(text)) self.textlog.yview(END) self.master.update_idletasks() def buttonquit(self): """ function to end the misery note: the pyplot windows are not closed. Maybe, it would be nice to keep track of those to close them """ set_hyperspectral_datafolder(self.foldername1) self.master.destroy( ) # destruction of root required for program to continue (to its end)
class MainFrame(tk.Frame): def __init__(self, master=None, db_session=None): super().__init__(master) self.master = master self.db_session = db_session self.board_tile_width = 100 x = self.master.winfo_screenwidth() // 2 - 150 y = self.master.winfo_screenheight() // 2 - 50 self.master.geometry("%dx%d+%d+%d" % (300, 275, x, y)) self.master.resizable(False, False) padx = 10 pady = 5 self.tabs = Notebook(self) self.tabs.grid(row=0, column=0, sticky="NW") self.tabs_game = tk.Frame(self.tabs) self.tabs.add(self.tabs_game, text="Game") self.start_frame = tk.Frame(self.tabs_game) self.start_frame.grid(row=0, column=0, pady=pady, sticky="NW") self.start_btn = tk.Button(self.start_frame, text="Play", command=self.start_game) self.start_btn.grid(row=0, column=0, padx=padx, pady=pady, sticky="NW") self.ai_difficulty = tk.IntVar(self, -1) for ai_difficulty in [((0, 0), "2 players", -1), ((1, 0), "Random AI", 0), ((0, 1), "Normal AI", 1)]: radio_btn = tk.Radiobutton( self.start_frame, variable=self.ai_difficulty, text=ai_difficulty[1], val=ai_difficulty[2], ) radio_btn.grid(row=ai_difficulty[0][1], column=ai_difficulty[0][0] + 1, pady=pady, sticky="NW") self.status_frame = tk.Frame(self.tabs_game) self.status_frame.grid(row=1, column=0, padx=padx, pady=pady, sticky="NW") self.status_frame.grid_remove() self.status_label = tk.Label(self.status_frame, text="") self.status_label.grid(row=0, column=0, pady=pady, sticky="NW") self.draw_btn = tk.Button(self.status_frame, text="Claim draw", command=self.claim_draw) self.draw_btn.grid(row=0, column=1, padx=padx, pady=pady, sticky="NE") self.draw_btn.grid_remove() self.moves_text = ScrolledText(self.status_frame, width=20, height=3, state=tk.DISABLED) self.moves_text.grid(row=1, column=0, pady=pady) self.promotion_piece = tk.StringVar(self, "Q") self.promotion_piece_selection_frame = tk.Frame(self.tabs_game) self.promotion_piece_selection_frame.grid(row=3, column=0, padx=padx, pady=pady) self.promotion_piece_selection_frame.grid_remove() promotion_label = tk.Label(self.promotion_piece_selection_frame, text="Promotion piece") promotion_label.grid(row=0, column=0, sticky="NW") for promotion_piece in [ (0, "Knight", "N"), (1, "Bishop", "B"), (2, "Rook", "R"), (3, "Queen", "Q"), ]: radio_btn = tk.Radiobutton( self.promotion_piece_selection_frame, variable=self.promotion_piece, command=self.on_promotion_piece_selected, text=promotion_piece[1], val=promotion_piece[2], ) radio_btn.grid(row=1, column=promotion_piece[0], pady=pady) self.view_control_btn_frame = tk.Frame(self.tabs_game) self.view_control_btn_frame.grid(row=3, column=0, padx=padx, pady=pady) self.view_control_btn_frame.grid_remove() self.previous_move_btn = tk.Button(self.view_control_btn_frame, text="Previous", command=self.previous_move) self.previous_move_btn.grid(row=0, column=0, padx=padx, pady=pady) self.next_move_btn = tk.Button(self.view_control_btn_frame, text="Next", command=self.next_move) self.next_move_btn.grid(row=0, column=1, padx=padx, pady=pady) self.save_game_btn = tk.Button(self.tabs_game, text="Save", command=self.db_save_game) self.save_game_btn.grid(row=4, column=0, padx=padx, pady=pady, sticky="NW") self.save_game_btn.grid_remove() self.tabs_database = tk.Frame(self.tabs) self.tabs.add(self.tabs_database, text="Database") self.pgn_label = tk.Label(self.tabs_database, text="Portable Game Notation") self.pgn_label.grid(row=0, column=0, padx=padx, sticky="NW") self.pgn_text = ScrolledText(self.tabs_database, width=20, height=3) self.pgn_text.grid(row=1, column=0, padx=padx, pady=pady, sticky="NW") pgn_btn_frame = tk.Frame(self.tabs_database) pgn_btn_frame.grid(row=2, column=0, pady=pady, sticky="NW") import_btn = tk.Button(pgn_btn_frame, text="Import", command=self.import_pgn) import_btn.grid(row=0, column=0, padx=padx, pady=pady) export_btn = tk.Button(pgn_btn_frame, text="Export", command=self.export_pgn) export_btn.grid(row=0, column=1, padx=padx, pady=pady) view_btn = tk.Button(pgn_btn_frame, text="View", command=lambda: self.start_game(view_mode=True)) view_btn.grid(row=0, column=2, padx=padx, pady=pady) game_list_frame = tk.Frame(self.tabs_database) game_list_frame.grid(row=4, column=0, pady=pady) game_list_label = tk.Label(game_list_frame, text="Stored games") game_list_label.grid(row=0, column=0, padx=padx, sticky="NW") self.game_combobox = Combobox(game_list_frame, state="readonly") self.game_combobox.grid(row=1, column=0, padx=padx, pady=pady) load_btn = tk.Button(game_list_frame, text="Load", command=self.db_load_pgn) load_btn.grid(row=1, column=1, padx=padx, pady=pady) delete_btn = tk.Button(game_list_frame, text="Delete", command=self.db_delete_game) delete_btn.grid(row=1, column=2, padx=padx, pady=pady) self.db_load_games() self.tabs_settings = tk.Frame(self.tabs) self.tabs.add(self.tabs_settings, text="Settings") self.enable_sounds = tk.BooleanVar( value=self.db_get_setting("enable_sounds", "0") == "1") if system() != "Linux": sound_checkbtn = tk.Checkbutton( self.tabs_settings, text="Enable sounds", variable=self.enable_sounds, command=lambda: self.db_store_setting( "enable_sounds", "1" if self.enable_sounds.get() else "0"), ) sound_checkbtn.grid(row=0, column=0, padx=padx, pady=pady, sticky="NW") self.theme = tk.StringVar(self, self.db_get_setting("theme", "Classic")) theme_frame = tk.Frame(self.tabs_settings) theme_frame.grid(row=1, column=0, pady=pady) theme_label = tk.Label(theme_frame, text="Theme") theme_label.grid(row=0, column=0, padx=padx, sticky="NW") theme_combobox = Combobox( theme_frame, textvariable=self.theme, values=["Classic", "Dark"], state="readonly", ) theme_combobox.bind("<<ComboboxSelected>>", self.update_theme) theme_combobox.grid(row=1, column=0, padx=padx, pady=pady) credits_btn = tk.Button( self.tabs_settings, text="Credits", command=lambda: tk.messagebox.showinfo( title="Credits", message="%s by %s\n\nIcons:\n%s" % ( self.master.title(), "Kekalainen ([email protected])", "Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0)", ), ), ) credits_btn.grid(row=2, column=0, padx=padx, pady=pady, sticky="NW") quit_btn = tk.Button(self, text="Quit", command=self.master.destroy) quit_btn.grid(row=1, column=0, padx=padx, pady=pady, sticky="NW") self.grid() def import_pgn(self): path = filedialog.askopenfilename( title="Import a PGN file", filetypes=[("PGN file", "*.pgn")], ) if path: with open(path) as f: self.pgn_text.delete(1.0, tk.END) self.pgn_text.insert(1.0, f.read()) def export_pgn(self): path = filedialog.asksaveasfilename( title="Export a PGN file", filetypes=[("PGN file", "*.pgn")], defaultextension=".pgn", ) if path: with open(path, "w") as f: f.write(self.pgn_text.get(1.0, tk.END)) def db_save_game(self): name = simpledialog.askstring("Save PGN", "Enter a name for the game.", parent=self) if name: game = GameModel(name=name, pgn=self.moves_text.get(1.0, tk.END)) self.db_session.add(game) self.db_session.commit() self.db_load_games() def db_load_games(self): self.db_games = (self.db_session.query(GameModel).order_by( GameModel.id.desc()).all()) names = [] for game in self.db_games: names.append(game.name) self.game_combobox.config(values=names) if names: self.game_combobox.set(names[0]) else: self.game_combobox.set("") def db_load_pgn(self): if len(self.db_games) > 0: game = self.db_games[self.game_combobox.current()] self.pgn_text.delete(1.0, tk.END) self.pgn_text.insert(1.0, game.pgn) def db_delete_game(self): if len(self.db_games) > 0: game = self.db_games[self.game_combobox.current()] self.db_session.delete(game) self.db_session.commit() self.db_load_games() def db_get_setting(self, name, fallback): setting = (self.db_session.query(SettingModel).filter( SettingModel.name == name).first()) if setting: return setting.value return fallback def db_store_setting(self, name, value): setting = (self.db_session.query(SettingModel).filter( SettingModel.name == name).first()) if setting: setting.value = value else: setting = SettingModel(name=name, value=str(value)) self.db_session.add(setting) self.db_session.commit() def play_move_sound(self, undo=False): """Plays a sound for a moving piece.""" threading.Thread( target=playsound, args=("src/audio/move_" + ("2" if (undo and not self.game.white_to_move) or (not undo and self.game.white_to_move) else "1") + ".mp3", ), daemon=True, ).start() def update_theme(self, event=None): """Stores the selected theme and updates the board, if necessary.""" theme = self.theme.get() self.db_store_setting("theme", theme) if hasattr(self, "game"): colors = ["#542E1D", "#EFD8B0"] if theme == "Dark": colors = ["#9C9C9C", "#4A4A4A"] self.board_frame.tile_colors = colors self.board_frame.draw_board() def on_game_update(self): game_over_previously = "to move" not in self.status_label["text"] text = "" if self.game.check and not self.game.checkmate: text += "Check. " if self.game.white_to_move: text += "White to move." else: text += "Black to move." if self.game.checkmate: text = "Checkmate." if not self.game.white_to_move: text += " White wins." else: text += " Black wins." elif self.game.stalemate: text = "Stalemate. Draw." elif self.game.draw: text = "Draw." self.status_label["text"] = text move_log = "" for i in range(len(self.game.an_moves)): if i % 2 == 0: move_log += str(i // 2 + 1) + ". " move_log += self.game.an_moves[i] if i % 2 == 0: move_log += " " else: move_log += "\n" if self.game.checkmate or self.game.stalemate or self.game.draw: move_log = move_log[0:len(move_log) - 1] + " " if self.game.checkmate: if self.game.white_to_move: move_log += "0-1" else: move_log += "1-0" else: move_log += "1/2-1/2" self.moves_text.config(state=tk.NORMAL) self.moves_text.delete(1.0, tk.END) self.moves_text.insert(1.0, move_log) self.moves_text.config(state=tk.DISABLED) self.moves_text.yview(tk.END) if not self.game.draw and True in self.game.can_claim_draw: self.draw_btn.grid() else: self.draw_btn.grid_remove() if hasattr(self.game, "ai") and self.game.white_to_move: self.board_frame.draw_pieces() moves_length = len(self.game.an_moves) if self.enable_sounds.get( ) and self.previous_moves_length != moves_length: undo = self.previous_moves_length > moves_length if hasattr(self.game, "ai") and undo and not game_over_previously: self.play_move_sound(not undo) self.play_move_sound(undo) self.previous_moves_length = moves_length def on_promotion_piece_selected(self): if hasattr(self, "game"): self.game.board.promotion_piece = self.promotion_piece.get() def start_game(self, view_mode=False): if not hasattr(self, "game"): self.previous_moves_length = 0 if view_mode: self.active_pgn = re.findall( "((?!-)[a-zA-Z0-]+[0-9]?\w+(?!\.)=?[N|B|R|Q]?)(?![^{]*})(?![^[]*])", self.pgn_text.get(1.0, tk.END), ) if not self.active_pgn: return self.active_pgn_index = 0 self.previous_move_btn.configure(state=tk.DISABLED) self.next_move_btn.configure(state=tk.NORMAL) self.view_control_btn_frame.grid() self.tabs.select(self.tabs_game) else: self.promotion_piece_selection_frame.grid() self.save_game_btn.grid() self.game = Game( on_update=self.on_game_update, ai_difficulty=self.ai_difficulty.get() if not view_mode else -1, ) self.game.board.promotion_piece = self.promotion_piece.get() self.board_dimension = self.game.board.width * self.board_tile_width self.board_window = tk.Toplevel(master=self) x = self.master.winfo_x() - self.board_dimension - 4 y = self.master.winfo_y() self.board_window.geometry( "%dx%d+%d+%d" % (self.board_dimension, self.board_dimension, x, y)) self.board_window.resizable(False, False) self.board_frame = BoardFrame( self.board_window, game=self.game, tile_width=self.board_tile_width, view_mode=view_mode, ) self.update_theme() self.master.bind("<Configure>", self.board_window_follow) self.board_window.protocol("WM_DELETE_WINDOW", self.on_board_window_close) self.start_frame.grid_remove() self.status_frame.grid() self.on_game_update() def claim_draw(self): if hasattr(self, "game"): if self.game.claim_draw(): self.board_frame.deselect_tile() self.draw_btn.grid_remove() def next_move(self): n = len(self.active_pgn) if self.active_pgn_index < n: self.game.move_piece_an(self.active_pgn[self.active_pgn_index]) self.board_frame.draw_pieces() self.active_pgn_index += 1 if self.active_pgn_index == n: self.next_move_btn.configure(state=tk.DISABLED) self.previous_move_btn.configure(state=tk.NORMAL) def previous_move(self): if self.active_pgn_index > 0: self.game.undo_move() self.board_frame.draw_pieces() self.active_pgn_index -= 1 if self.active_pgn_index == 0: self.previous_move_btn.configure(state=tk.DISABLED) self.next_move_btn.configure(state=tk.NORMAL) def board_window_follow(self, event=None): x = self.master.winfo_x() - self.board_dimension - 4 y = self.master.winfo_y() self.board_window.geometry("+%d+%d" % (x, y)) def on_board_window_close(self): delattr(self, "game") self.master.unbind("<Configure>") self.board_window.destroy() self.start_frame.grid() self.status_frame.grid_remove() self.view_control_btn_frame.grid_remove() self.promotion_piece_selection_frame.grid_remove() self.save_game_btn.grid_remove() self.draw_btn.grid_remove()
class Console(tkinter.Frame): def __init__(self, parent, settings, max_lines=200, **kwargs): super().__init__(parent, **kwargs) self.settings = settings self.max_lines = max_lines self.console = ScrolledText(self, bg=Theme.CONSOLE_BG, highlightbackground=Theme.BG, highlightcolor=Theme.BG, wrap=tkinter.CHAR, width=40, height=10, state='disabled', relief='flat') # Despite setting height above, this widget gets expanded fully, # if the canvas is smaller than the height, will look odd. self.console.pack(fill=tkinter.BOTH, expand=True) self.font = ('Helvetica', 11, 'bold') # Text color self.console.tag_config('TEXT', foreground=Theme.CONSOLE_TEXT, font=('Helvetica', 11), spacing1=5, spacing3=5) self.console.tag_config('TEXT_ALT', foreground=Theme.CONSOLE_TEXT, background=Theme.CONSOLE_BG_ALT, selectbackground='SystemHighlight', font=('Helvetica', 11), spacing1=5, spacing3=5) # Why does setting 'background' causes highlighted text color to be transparent? # https://web.archive.org/web/20201112014139id_/https://effbot.org/tkinterbook/tkinter-widget-styling.htm self.alt_bg = False # Logging colors self.console.tag_config('INFO', foreground=Theme.LOG_INFO) self.console.tag_config('DEBUG', foreground=Theme.LOG_DEBUG) self.console.tag_config('ERROR', foreground=Theme.LOG_ERROR) self.console.tag_config('WARNING', foreground=Theme.LOG_WARNING) self.console.tag_config('CRITICAL', foreground=Theme.LOG_CRITICAL) self.console.focus() if not self.settings['irc']['username']: self.welcome() self.after(100, self.pooling) def pooling(self): while 1: try: message = QUEUE.get(block=False) self.insert(message) except queue.Empty: break self.after(100, self.pooling) def insert(self, text): self.console.configure(state='normal') # Allow writing try: # Tcl can't render some characters if isinstance(text, Message): self.alt_bg = not self.alt_bg user_color = 'lightblue1' if not text.tags.get('color') else text.tags.get('color') username_tag = f'{text.sender}{"alt_bg" if self.alt_bg else ""}' self.console.tag_config(username_tag, font=self.font, foreground=user_color, background=Theme.CONSOLE_BG_ALT if self.alt_bg else Theme.CONSOLE_BG, selectbackground='SystemHighlight', spacing1=5, spacing3=5) self.console.insert(tkinter.END, text.sender, username_tag) self.console.insert(tkinter.END, f': {text.message}\n', 'TEXT_ALT' if self.alt_bg else 'TEXT') else: message = LOGGER.handlers[1].format(text) # This is not a good way to access this self.console.insert(tkinter.END, f'{message}\n', text.levelname) except tkinter.TclError as e: if isinstance(text, Message): # Replace every char outside of Tcl's allowed range with the ? char. text.message = ''.join((ch if ord(ch) <= 0xFFFF else '\uFFFD') for ch in text.message) self.console.insert(tkinter.END, f': {text.message}\n', 'TEXT') else: self.console.insert(tkinter.END, f'{e}\n', 'ERROR') #https://stackoverflow.com/questions/4609382/getting-the-total-number-of-lines-in-a-tkinter-text-widget self.line_count = int(self.console.index('end-2c').split('.')[0]) if self.line_count > self.max_lines: self.console.delete(1.0, 2.0) self.console.configure(state='disabled') # Disallow writing self.console.yview(tkinter.END) def welcome(self): self.font = ('TkDefaultFont', 11, 'bold') self.console.tag_configure('lightblue', foreground='lightblue1', font=self.font, justify='center') self.console.tag_configure('white', foreground='white', font=self.font) self.console.tag_configure('orange', foreground='orange', font=self.font) self.console.tag_configure('pink', foreground='#FFC8D7', font=self.font) self.console.tag_configure('red', foreground='red', font=self.font, spacing1=2, spacing3=2) self.console.tag_config('grey', foreground='grey', font=('TkDefaultFont', 8, 'bold')) self.console.configure(state='normal') self.console.insert(tkinter.END, '~ Welcome to parky\'s twitch bot! ~\n\n', 'lightblue') self.console.insert(tkinter.END, '\n', 'white') self.console.insert(tkinter.END, 'Quick setup:\n', 'orange') self.console.insert(tkinter.END, '\n', 'white') self.console.insert(tkinter.END, '1', 'red') self.console.insert(tkinter.END, '. Click on the "', 'white') self.settings_img = tkinter.PhotoImage(data=Theme.SETTINGS_ICON) self.console.image_create(tkinter.END, image=self.settings_img) self.console.insert(tkinter.END, '" button.\n', 'white') self.console.insert(tkinter.END, '2', 'red') self.console.insert(tkinter.END, '. Fill in IRC fields to gain chat access!\n', 'white') self.console.insert(tkinter.END, '3', 'red') self.console.insert(tkinter.END, '. ', 'white') self.console.insert(tkinter.END, 'Fill in Twitch API to gain access to channel metadata, such as current title, game, uptime, followers... ', 'white') self.console.insert(tkinter.END, '(optional)\n', 'grey') self.console.insert(tkinter.END, '\n', 'TEXT') self.console.configure(state='disabled')
class FrontWindow: def __init__(self, root, port): self.root = root self.port = port self.Frame = tk.Frame(self.root, height=600, width=700) self.Frame.pack(side=tk.RIGHT) self.textField = ScrolledText(self.Frame) self.textField.place(relx=0.05, rely=0.05, height=525, width=350) self.statusLabel = tk.Label(self.Frame) self.combobox = ttk.Combobox(self.Frame) self.spinbox = tk.Spinbox(self.Frame, from_=1.0, to=10.0, increment=0.5) self.commandInput = tk.Entry(self.Frame) self.ending = tk.IntVar() self.openClosePortButton = tk.Button(self.Frame) def addPortCombobox(self): self.combobox['font'] = ('Arial', 14) self.combobox['state'] = 'readonly' self.combobox['values'] = ('COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8') self.combobox.current(2) self.combobox.place(relx=0.8, rely=0.05, width=100) def addPortSpinbox(self): defaultValue = tk.StringVar() defaultValue.set('5.0') self.spinbox['textvariable'] = defaultValue self.spinbox['font'] = ('Arial', 14) self.spinbox['state'] = 'readonly' self.spinbox.place(relx=0.8, rely=0.15, width=100) def addCommandInputField(self): self.commandInput['font'] = ('Arial', 14) self.commandInput.place(relx=0.55, rely=0.65, width=200) def addCheckbutton(self): checkbutton = tk.Checkbutton(self.Frame, text='Adjust CR+LF', font=('Arial', 13), variable=self.ending) checkbutton.place(relx=0.55, rely=0.7) # labels def addStatusLabel(self): self.statusLabel.destroy() if self.port.is_open: labelText = 'opened' color = 'green' else: labelText = 'closed' color = 'red' self.statusLabel = tk.Label(self.Frame, font=('Arial', 15), text=labelText, fg=color) self.statusLabel.place(relx=0.8, rely=0.36) def addPortNameLabel(self): portNameLabel = tk.Label(self.Frame, font=('Arial', 15), text='Port name: ') portNameLabel.place(relx=0.6, rely=0.05) def addPortTimeoutLabel(self): portTimeoutLabel = tk.Label(self.Frame, font=('Arial', 15), text='Port timeout: ') portTimeoutLabel.place(relx=0.58, rely=0.15) def addPortStatusLabel(self): portStatusLabel = tk.Label(self.Frame, font=('Arial', 15), text='Port status: ') portStatusLabel.place(relx=0.6, rely=0.36) def addCommandLabel(self): commandLabel = tk.Label(self.Frame, font=('Arial', 15), text='Insert command: ') commandLabel.place(relx=0.58, rely=0.59) # buttons callbacks def callbackOptionsBtn(self): self.port.port = str(self.combobox.get()) OptionsWindow(self.root, self.port) def callbackOpenClosePortBtn(self): if not self.port.is_open: self.port.port = str(self.combobox.get()) self.port.timeout = float(self.spinbox.get()) try: self.port.open() except serial.serialutil.SerialException as e: print(e) errorMsg = 'Could not open port: ' + str(self.port.port) messagebox.showinfo('Error', errorMsg) return status = 'Close port' else: try: self.port.close() except serial.serialutil.SerialException as e: print(e) errorMsg = 'Could not close port: ' + str(self.port.port) messagebox.showinfo('Error', errorMsg) return status = 'Open port' self.openClosePortButton.config(text=status) self.addStatusLabel() def getInputFromPort(self): wasLastStringEmpty = True if self.port.is_open: timeout = float(self.spinbox.get()) start = time.time() while True: inputFromPort = self.port.readline() if inputFromPort == b'': if wasLastStringEmpty: if time.time() - start >= timeout: infoMsg = 'Timeout expired' messagebox.showinfo('Message', infoMsg) break else: continue else: break else: wasLastStringEmpty = False self.textField.insert(tk.END, inputFromPort) self.textField.yview(tk.END) self.root.update() else: errorMsg = 'Port ' + str(self.combobox.get()) + ' is not open!' messagebox.showinfo('Error', errorMsg) def writeToPort(self): if self.port.is_open: command = self.commandInput.get() if self.ending.get(): command += chr(13) + chr(10) self.port.write(command.encode('utf-8')) self.commandInput.delete(0, 'end') else: errorMsg = 'Port ' + str(self.combobox.get()) + ' is not open!' messagebox.showinfo('Error', errorMsg) def callbackSaveBtn(self): fileTypes = (('text files', '*.txt'), ('all files', '*.*')) filename = filedialog.asksaveasfilename(title='Select file', filetypes=fileTypes) try: txtFile = open(filename, 'w+') except FileNotFoundError as e: print(e) return txtFile.write(self.textField.get('1.0', tk.END)) txtFile.close() # buttons definitions def addOptionsButton(self): optionsButton = tk.Button(self.Frame, text='More options', font=('Arial', 15), command=self.callbackOptionsBtn) optionsButton.place(relx=0.67, rely=0.23, width=150) def addOpenClosePortButton(self): self.openClosePortButton['text'] = 'Open Port' self.openClosePortButton['font'] = ('Arial', 15) self.openClosePortButton['command'] = self.callbackOpenClosePortBtn self.openClosePortButton.place(relx=0.67, rely=0.45, width=150) def addReadFromPortButton(self): readFromPortButton = tk.Button(self.Frame, text='Read', font=('Arial', 15), command=self.getInputFromPort) readFromPortButton.place(relx=0.7, rely=0.77, width=100) def addWriteToPortButton(self): writeToPortButton = tk.Button(self.Frame, text='Write', font=('Arial', 15), command=self.writeToPort) writeToPortButton.place(relx=0.85, rely=0.64, width=100) def addSaveButton(self): saveButton = tk.Button(self.Frame, font=('Arial', 15), text='Save', command=self.callbackSaveBtn) saveButton.place(relx=0.6, rely=0.9, width=100) def addExitButton(self): exitButton = tk.Button(self.Frame, text='Exit', font=('Arial', 15), command=lambda: self.root.destroy()) exitButton.place(relx=0.8, rely=0.9, width=100)
class TkGraphicInterface: def __init__(self, on_stop=Callable[[None], None]): self._on_stop = on_stop self._thread = None self._ui_ready = False self._logger_buffer = list() self._window = None self._logger_widget = None self._connected = False self._client_name = False def start_ui(self): self._thread = threading.Thread(target=self._run_ui) self._thread.start() def kill_ui(self): self._window.destroy() self._window = None self._thread.join() def on_emit_log(self, message: str): if not self._ui_ready: self._logger_buffer.append(message) elif len(self._logger_buffer) > 0: self._logger_buffer.append(message) for x in self._logger_buffer: self._update_logger(x) self._logger_buffer.clear() else: self._update_logger(message) # noinspection PyTypeChecker def on_update_status(self, state: Tuple[bool, str]): if self._connected == state[0] or self._client_name == state[1]: pass self._update_status(state[0], state[1]) def _run_ui(self): self._init_ui() self._window.mainloop() def _init_ui(self): self._window = tkinter.Tk() self._window.tk.call('tk', 'scaling', 4.0) self._window.title("VFT Device Server") self._window.geometry("600x400+100+100") print(font.families()) head_label = tkinter.Label( self._window, text="VFT Device Server", font=font.Font(family="fixed", size=30), ) head_label.grid(row=0, column=0) version_label = tkinter.Label( self._window, text="GUI version 1.0", ) version_label.grid(row=0, column=1) self._logger_widget = ScrolledText( self._window, state="disabled", font="TkFixedFont", width=83, height=16, ) self._logger_widget.place(x=0, y=200) self._ui_ready = True self.on_emit_log("[o] tkinter-ui ready.") def _update_status(self, connected: bool, client_name: str): pass def _update_logger(self, message: str): self._logger_widget.configure(state="normal") self._logger_widget.insert(tkinter.END, message + "\n") self._logger_widget.configure(state="disabled") self._logger_widget.yview(tkinter.END) def _stop_server(self): self.kill_ui()
class NamedWindow(object): """ 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 = tkinter.font.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" % (old_div(rgb[0], 256), old_div( rgb[1], 256), old_div(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 list(fg_color_codes.keys()): color = self.convertColor(fg_color_codes[ck]) self._txt.tag_config(ck, foreground=color) for ck in list(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) == tuple: msg = msg[0] if type(msg) == bytes: 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 clientUI(): def __init__(self, args, q_send, q_receive_join, q_receive_text, q_receive_leave, b): self.first_click = True; self.args = args self.q_send = q_send self.q_receive_join = q_receive_join self.q_receive_text = q_receive_text self.q_receive_leave = q_receive_leave self.b = b def start(self): print("Starting clientUI...") self.initDisplay() self.ui_messages.insert(tkinter.END, "%s has joined the chat room...\n" % self.args.username) self.ui_input.insert(tkinter.END, "<Enter message>") self.ui_top.after(100, self.receiveMsg) # This call to mainloop() is blocking and will last for the lifetime # of the GUI. self.ui_top.mainloop() # Should only get here after destroy() is called on ui_top print("Stopping clientUI...") def initDisplay(self): self.ui_top = tkinter.Tk() self.ui_top.wm_title("Chat Room 1.0 - %s" % self.args.username) self.ui_top.resizable('1','1') self.ui_top.protocol("WM_DELETE_WINDOW", self.eventDeleteDisplay) self.ui_messages = ScrolledText( master=self.ui_top, wrap=tkinter.WORD, width=50, # In chars height=25) # In chars self.ui_input = tkinter.Text( master=self.ui_top, wrap=tkinter.WORD, width=50, height=4) # Bind the button-1 click of the Entry to the handler self.ui_input.bind('<Button-1>', self.eventInputClick) #self.ui_top.after(200, receiveMsg) self.ui_button_send = tkinter.Button( master=self.ui_top, text="Send", command=self.sendMsg) # Compute display position for all objects self.ui_messages.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_input.pack(side=tkinter.TOP, fill=tkinter.BOTH) self.ui_button_send.pack(side=tkinter.LEFT) # SEND button pressed def sendMsg(self): # Get user input (minus newline character at end) msg = self.ui_input.get("0.0", tkinter.END+"-1c") if len(msg) > 0: self.q_send.put(msg) print("UI: Got text: '%s'" % msg) # Add this data to the message window self.ui_messages.insert(tkinter.INSERT, "%s: %s\n" % (self.args.username, msg)) self.ui_messages.yview(tkinter.END) # Auto-scrolling # Clean out input field for new data self.ui_input.delete("0.0", tkinter.END) # Receive messages from other clients def receiveMsg(self): #print ("Inside receiveMsg") try: username_join = self.q_receive_join.get(block=False) join_msg = True; except queue.Empty: join_msg = False; try: username_text, text = self.q_receive_text.get(block=False) text_msg = True; except queue.Empty: text_msg = False; try: username_leave = self.q_receive_leave.get(block=False) leave_msg = True; except queue.Empty: leave_msg = False; if join_msg: self.ui_messages.insert(tkinter.INSERT, "%s has joined the chat room...\n" % username_join) self.ui_messages.yview(tkinter.END) # Auto-scrolling elif text_msg: self.ui_messages.insert(tkinter.INSERT, "%s: %s\n" % (username_text, text)) self.ui_messages.yview(tkinter.END) # Auto-scrolling elif leave_msg: self.ui_messages.insert(tkinter.INSERT, "%s has left the chat room...\n" % username_leave) self.ui_messages.yview(tkinter.END) # Auto-scrolling self.ui_top.after(100, self.receiveMsg) # FILE button pressed def sendFile(self): file = askopenfilename() if(len(file) > 0 and os.path.isfile(file)): print("UI: Selected file: %s" % file) else: print("UI: File operation canceled") # Event handler - User closed program via window manager or CTRL-C def eventDeleteDisplay(self): print("UI: Closing") global client_active client_active = False; # Continuing closing window now self.ui_top.destroy() # Event handler - User clicked inside the "ui_input" field def eventInputClick(self, event): if(self.first_click): # If this is the first time the user clicked, # clear out the tutorial message currently in the box. # Otherwise, ignore it. self.ui_input.delete("0.0", tkinter.END) self.first_click = False;
class ConsoleUi: """Poll messages from a logging queue and display them in a scrolled text widget""" def __init__(self, frame): self.frame = frame # Create a ScrolledText wdiget self.scrolled_text = ScrolledText(frame, state='disabled', height=12) self.scrolled_text.grid(row=0, column=0, sticky=(N, S, W, E)) self.scrolled_text.configure(font='TkFixedFont') self.scrolled_text.tag_config('INFO', foreground='black') self.scrolled_text.tag_config('DEBUG', foreground='gray') self.scrolled_text.tag_config('WARNING', foreground='orange') self.scrolled_text.tag_config('ERROR', foreground='red') self.scrolled_text.tag_config('CRITICAL', foreground='red', underline=1) self.scrolled_text.tag_config( 'COPYOVER', foreground='blue', ) self.scrolled_text.tag_config( 'COPYNEW', foreground='green', ) self.scrolled_text.tag_config( 'DELETE', foreground='red', ) self.scrolled_text.tag_config( 'MOVE', foreground='magenta', ) # Start polling messages from the queue self.frame.after(100, self.poll_log_queue) def display(self, record): msg = record # Send special message to clear the log if msg == "__clear__": self.scrolled_text.configure(state='normal') self.scrolled_text.delete(1.0, tk.END) self.scrolled_text.configure(state='disabled') return self.scrolled_text.configure(state='normal') self.scrolled_text.insert(tk.END, msg[1] + '\n', msg[0]) self.scrolled_text.configure(state='disabled') # Autoscroll to the bottom self.scrolled_text.yview(tk.END) def poll_log_queue(self): # Check every 100ms if there is a new message in the queue to display while True: try: record = msg_queue.get(block=False) except queue.Empty: break else: self.display(record) self.frame.after(100, self.poll_log_queue)
class ChatWindow(Tk): def __init__(self, sock=None, peer=None, address=None): failure = None self.peer = Peer() if peer: self.peer.host, self.peer.port = peer self.nick = 'me' self.mode = Mode.msgpack self.sent_header = False self.address = address if sock: sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1) self.peer.sock = sock self.initiator = False header = self.peer.recv(5) if header[1:] != b'FLEX': Socket.abort(self.peer.sock, (b'Unrecognized protocol header\n')) print('Unrecognized protocol:', header) sys.exit(0) if header[0] == 0: self.mode = Mode.text elif header[0] == 0xa4: self.mode = Mode.msgpack elif header[0] == 0x22: self.mode = Mode.json else: Socket.abort(self.peer.sock, (b'Unrecognized protocol mode\n')) print('Unrecognized protocol mode:', header[0]) sys.exit(0) elif peer: self.initiator = True try: self.connect() except Exception as e: failure = str(e) else: raise Exception("Missing required parameter") Tk.__init__(self) self.title(self.peer.name) #self.root = Tk() #self.main = Frame(self.root) self.main = Frame(self) self.main.pack(expand=True, fill=BOTH) self.chat_Text = ScrolledText(self.main) self.chat_Text.pack(expand=True, fill=BOTH) self.chat_Text['height'] = 10 self.chat_Hyperlink = Hyperlink(self.chat_Text) self.send_Frame = Frame(self.main) self.send_Frame.pack(fill=X) self.send_Text = Text(self.send_Frame) self.send_Text.pack(side=LEFT, expand=True, fill=X) self.send_Text['height'] = 2 self.send_Text.bind('<Shift-Return>', self.send_Newline) self.send_Text.bind('<Control-Return>', self.send_Newline) self.send_Text.bind('<Return>', self.send_Action) self.send_Button = Button(self.send_Frame, text='Send', command=self.send_Action) self.send_Button.pack(side=LEFT) self.status_Label = Label(self.main, text='Peer: {}'.format(self.peer.name)) self.status_Label.pack() self.send_Text.focus() if failure: self.disable(failure) else: try: #this only works in linux for some reason self.checker = None self.tk.createfilehandler(self.peer.sock, _tkinter.READABLE, self.eventChecker) except: #rescue windows traceback.print_exc() print('Windows mode!') sock.setblocking(False) self.checker = self.main.after(100, self.eventChecker) @property def mode(self): return self.receive_mode @mode.setter def mode(self, mode): self.receive_mode = mode self.send_mode = mode @property def receive_mode(self): return self.receive_proto.mode @receive_mode.setter def receive_mode(self, mode): if mode == Mode.msgpack: self.receive_proto = Msgpack() elif mode == Mode.json: self.receive_proto = JSON() elif mode == Mode.text: self.receive_proto = PText() else: raise ValueError('Unknown protocol mode: {}'.format(mode)) @property def send_mode(self): return self.send_proto.mode @send_mode.setter def send_mode(self, mode): if mode == Mode.msgpack: self.send_proto = Msgpack() elif mode == Mode.json: self.send_proto = JSON() elif mode == Mode.text: self.send_proto = PText() else: raise ValueError('Unknown protocol mode: {}'.format(mode)) def send_Newline(self, *args): # Let the default handler make a newline. This exists # just to prevent the send_Action handler from activating pass def destroy(self): Tk.destroy(self) def append_text(self, text): scroll = self.chat_Text.yview()[1] # find hyperlinks and link them i = text.find('http://') if i < 0: i = text.find('https://') if i >= 0: self.append_text(text[:i]) text = text[i:] # find the end of the link text i = text.find(' ') j = text.find('\n') if i < 0: i = j elif i >= 0 and j >= 0: i = min(i, j) if i >= 0: self.chat_Hyperlink.add(END, text[:i]) self.append_text(text[i:]) else: self.chat_Hyperlink.add(END, text) else: self.chat_Text.insert(END, text) if scroll >= 0.99: self.chat_Text.yview_moveto(1.0) def send_Action(self, *args): if not self.peer.sock: return 'break' text = self.send_Text.get('1.0', END) text = text.strip() self.send_Text.delete('1.0', END) if not text: return 'break' # /me is a "social command", so it's exempt from command processing if text[0] == '/' and not text.startswith('/me '): if text == '/bye': self.send_command('BYE ') elif text.startswith('/nick'): name = text[6:] if len(name): self.send_command('NICK', data=name) self.nick = name self.append_text('You are now known as {}\n'.format(name)) elif text == '/text': self.send_command('TEXT') self.send_mode = Mode.text self.send_header_once() elif text == '/json': self.send_command('JSON') self.send_mode = Mode.json self.send_header_once() elif text == '/msgpack': self.send_command('MPCK') self.send_mode = Mode.msgpack self.send_header_once() else: self.append_text('Unrecognized command: {}\n'.format(text)) else: self.append_text('{} {}: {}\n'.format(timestamp(), self.nick, text)) msg = self.send_proto.message(text, to=self.peer.name, From=str(self.address or '')) if _test_fragment: t = len(msg) h = t // 2 self.peer.sendall(msg[:h]) time.sleep(0.1) self.peer.sendall(msg[h:]) else: self.peer.sendall(msg) # Prevent default handler from adding a newline to the input textbox return 'break' def send_command(self, cmd, data=None): message = self.send_proto.command(cmd, data=data) self.peer.sendall(message) def send_header_once(self): if not self.sent_header and self.initiator: self.send_header() self.sent_header = True def send_header(self): data = { 'to': self.peer.name, 'from': str(self.address), 'options': [], } p = self.send_proto.header(data) if p is not None: self.peer.sendall(p) def update_peer_address(self, addr): self.peer.name = addr self.status_Label['text'] = 'Peer: ' + addr def process_header(self, header): self.update_peer_address(header['from']) def process_message(self, message): if 'to' in message and 'msg' not in message: self.process_header(message) elif 'cmd' in message: print('command:', message['cmd'], message['payload']) cmd = message['cmd'] self.process_command(cmd, more=message['payload']) else: if message['from']: self.update_peer_address(message['from']) self.append_text('{} {}: {}\n'.format(timestamp(), self.peer.nick, message['msg'])) def process_command(self, cmd, more=None): if cmd == 'JSON': self.receive_mode = Mode.json if more: self.process_packet(more) elif cmd == 'MPCK': self.receive_mode = Mode.msgpack if more: self.process_packet(more) elif cmd == 'TEXT': self.receive_mode = Mode.text elif cmd.startswith('NICK'): oldnick = self.peer.nick if more: nick = more.strip() else: nick = cmd[4:].strip() if nick not in ('me', '', self.nick): self.peer.nick = nick self.append_text('{} is now known as {}\n'.format( oldnick, self.peer.nick)) else: self.append_text( '{} tried to take the name {}, but that would be confusing.\n' .format(oldnick, nick)) elif cmd == 'BYE ': self.disable('Disconnecting: Bye\n') self.disconnect() else: self.append_text('Unrecognized command: {}'.format(cmd)) def process_packet(self, packet): self.receive_proto.feed(packet) i = self.receive_proto.read() for o in i: self.process_message(o) def eventChecker(self, *args): #could be (self, socket_fd, mask) try: try: packet = self.peer.recv(4096) except Exception as e: self.disable(str(e) + '\n') self.disconnect() traceback.print_exc() return print('packet:', packet, len(packet)) if len(packet) == 0: self.disable('Disconnected\n') self.disconnect() else: self.process_packet(packet) finally: if self.checker != None: self.checker = self.main.after(100, self.eventChecker) def connect(self): self.peer.connect() header = self.send_proto.first_packet self.peer.send(header) self.send_header_once() def disconnect(self): self.tk.deletefilehandler(self.peer.sock) self.peer.sock.close() self.peer.sock = None def disable(self, message): self.append_text(timestamp() + ' ' + message) self.chat_Text.yview_moveto(1.0) self.send_Text.config(state='disabled') self.send_Button.config(state='disabled') @staticmethod def give_socket(sock): print("I got a socket:", sock) wnd = ChatWindow(sock=sock) wnd.main.mainloop() @staticmethod def new_chat(peer, address): print("New peer:", peer) wnd = ChatWindow(peer=peer, address=address) wnd.main.mainloop()
class Console(tkinter.Frame): def __init__(self, parent, **kwargs): super().__init__(parent, **kwargs) self.console = ScrolledText(self, bg=Theme.CONSOLE_BG, wrap=tkinter.CHAR, width=40, height=10, state='disabled') # Despite setting height above, this widget gets expanded fully, # if the canvas is smaller than the height, will look odd. self.console.pack(fill=tkinter.BOTH, expand=True) # Text color self.console.tag_config('TEXT', foreground=Theme.HL, font='bold') # Logging colors self.console.tag_config('INFO', foreground=Theme.LOG_INFO) self.console.tag_config('DEBUG', foreground=Theme.LOG_DEBUG) self.console.tag_config('ERROR', foreground=Theme.LOG_ERROR) self.console.tag_config('WARNING', foreground=Theme.LOG_WARNING) self.console.tag_config('CRITICAL', foreground=Theme.LOG_CRITICAL) self.console.focus() self.after(100, self.pooling) def pooling(self): while 1: try: message = QUEUE.get(block=False) self.insert(message) except queue.Empty: break self.after(100, self.pooling) def insert(self, text): self.console.configure(state='normal') # Allow writing try: # Tcl can't render some characters if isinstance(text, Message): self.console.tag_config(text.sender, font='bold', foreground=text.tags.get('color', 'lightblue1')) self.console.insert(tkinter.END, text.sender, text.sender) self.console.insert(tkinter.END, f': {text.message}\n', 'TEXT') else: message = LOGGER.handlers[1].format(text) # This is not a good way to access this self.console.insert(tkinter.END, f'{message}\n', text.levelname) except tkinter.TclError as e: if isinstance(text, Message): # Replace every char outside of Tcl's allowed range with the ? char. text.message = ''.join((ch if ord(ch) <= 0xFFFF else '\uFFFD') for ch in text.message) self.console.insert(tkinter.END, f': {text.message}\n', 'TEXT') else: self.console.insert(tkinter.END, f'{e}\n', 'ERROR') self.console.configure(state='disabled') # Disallow writing self.console.yview(tkinter.END)