def attr_list_entry_callback(self, _event, attrib, entry_box: ttk.Entry, selected_widget): if selected_widget is not None: values: str = entry_box.get() values.strip(' ') if values.startswith('[') and values.endswith(']'): values = values[1:-1].strip(' ') value_list = [] while len(values) > 0: if values[0] == "'": next_value = values[1:].split("'")[0] value_list.append(next_value) values = values[(2 + len(next_value)):].strip(' ') if values.startswith(','): values = values[1:].strip(' ') else: entry_box.delete(0, tkinter.END) entry_box.insert(0, str(getattr(selected_widget, attrib))) raise Exception( 'entry should be a list of str, comma separated') else: value_list = [values] return_value = self.pyted_core.update_widget_attribute( selected_widget, attrib, value_list) # self.update_attr_frame() if selected_widget.tk_name is not None: self.handles.place_selected_widget_handles( selected_widget.tk_name) if return_value is not None: messagebox.showwarning( 'Renaming problem', 'Name already exists for another widget and Name not changed' )
class Content(Frame): def __init__(self, master): super().__init__(master) Label(self, text='Name:').grid(row=0, column=0, padx=5, sticky=SW) Label(self, text='Email:').grid(row=0, column=1, padx=5, sticky=SW) Label(self, text='Comments:').grid(row=2, column=0, padx=5, sticky=SW) self.entry_name = Entry(self, width=24, font=INPUT_FONT) self.entry_name.grid(row=1, column=0, padx=5) self.entry_email = Entry(self, width=24, font=INPUT_FONT) self.entry_email.grid(row=1, column=1, padx=5) self.text_comments = Text(self, width=50, height=10, font=INPUT_FONT) self.text_comments.grid(row=3, column=0, columnspan=2, padx=5) Button(self, text='Submit', command=self.submit) \ .grid(row=4, column=0, padx=5, pady=5, sticky=E) Button(self, text='Clear', command=self.clear) \ .grid(row=4, column=1, padx=5, pady=5, sticky=W) def submit(self): print(f'Name: {self.entry_name.get()}') print(f'Email: {self.entry_email.get()}') print(f'Comments: {self.text_comments.get(1.0, END)}') self.clear() messagebox.showinfo(title='Explore California Feedback', message='Comments Submitted!') def clear(self): self.entry_name.delete(0, END) self.entry_email.delete(0, END) self.text_comments.delete(1.0, END)
def set_date(this, calendar: Calendar, enty: Entry, frame: tkinter.Toplevel): enty.config(state="normal") enty.delete(0, "end") enty.insert(0, calendar.selection_get()) enty.config(state="disabled") frame.destroy()
def apagar_caracter_invalido(evento_char, ref_entry: ttk.Entry, caracteres_inválidos: str): """ Método estático para apagar os caracteres inseridos em um Entry no momento da digitação(evento Release). Pode ser usado sem instanciação. :param evento_char: event.char do evento. :param ref_entry: Referência do componente Entry. :param caracteres_inválidos: string de caracteres que deseja-se que sejam apagados. :return: none. """ if evento_char in caracteres_inválidos: ref_entry.delete(len(ref_entry.get()) - 1, 'end')
def select_file(entry: ttk.Entry, title: str, extension: str) -> None: window = tk.Toplevel() window.withdraw() path = tkinter.filedialog.askopenfilename(title=title, filetypes=(("{0} files".format(extension),"*.{0}".format(extension)),("all files","*.*"))) window.destroy() entry.delete(0, tk.END) entry.insert(0, path) return
def mac_auto(is_auto: tk.BooleanVar, entry: ttk.Entry): logging.info("mac auto clicked") if is_auto.get(): logging.info("disabling mac") entry.delete(0, tk.END) entry.insert(tk.END, "") entry.config(state=tk.DISABLED) else: entry.delete(0, tk.END) entry.insert(tk.END, "00:00:00:00:00:00") entry.config(state=tk.NORMAL)
class EntryOptionsWindow: def __init__(self, ls: str, tk: Tk, select_path=False) -> None: self.select_path = select_path self.List = ls self.Tk = tk self.Root = Toplevel(self.Tk) self.Root.withdraw() self.Frame = Frame(self.Root) self.Box = Listbox(self.Frame, selectmode='extended', width=54, height=24) for i in globals()[self.List]: self.Box.insert(END, i) self.Scroll = Scrollbar(self.Frame, command=self.Box.yview) self.Entry = Entry(self.Frame) self.ButtonAdd = Button(self.Frame, text='Добавить', command=self.__add_item) self.ButtonDel = Button(self.Frame, text='Удалить', command=self.__del_item) self.ButtonDone = Button(self.Frame, text='Готово', command=self.__save_list) self.ButtonExit = Button(self.Frame, text='Отмена', command=self.Root.destroy) def __add_item(self) -> None: if self.select_path: text = filedialog.askdirectory() else: text = self.Entry.get() if text: self.Box.insert(END, text) self.Entry.delete(0, END) def __del_item(self) -> None: select = list(self.Box.curselection()) select.reverse() for i in select: self.Box.delete(i) def __save_list(self) -> None: globals()[self.List] = list(self.Box.get(0, END)) self.Root.destroy() def main(self) -> None: center_win(self.Root, '500x400') self.Root.deiconify() self.Root.title(f'Editing {self.List}') self.Box.pack(side='left', expand=True) self.Scroll.pack(side='left', fill='y') self.Box.config(yscrollcommand=self.Scroll.set) self.Frame.pack(side='left', padx=10) if not self.select_path: self.Entry.pack(anchor='n') self.ButtonAdd.pack(fill='x') self.ButtonDel.pack(fill='x') self.ButtonDone.pack(fill='x') self.ButtonExit.pack(fill='x') self.Root.mainloop()
def login(self, username_entry: ttk.Entry, password_entry: ttk.Entry): data: dict = {"Usuario": str, "Contrasena": str} data["Usuario"] = username_entry.get() data["Contrasena"] = password_entry.get() response = self.makeRequest("POST", "login", data) if response.status_code == 200: self.interface_state = response.json()["account_type"] elif response.status_code == 401: messagebox.showerror("", "usuario o contrasena invalidos") password_entry.delete(0, tk.END) else: print("error en el server, help!!!")
class SelectKernel(Toplevel): """About Toplevel.""" def __init__(self, master): """Create the Toplevel to select an existing Jupyter kernel.""" Toplevel.__init__(self, master, class_=master.winfo_class(), padx=10, pady=10) self.resizable(True, False) self.title("Select existing kernel") self.columnconfigure(0, minsize=22) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) self.selected_kernel = '' Label(self, text="Select the .json connection file or enter the existing kernel's id:").grid(columnspan=4, sticky='w', pady=4) self.entry = Entry(self, width=10) self.entry.grid(row=1, columnspan=3, sticky='ew', pady=4) self.entry.bind('<Return>', lambda e: self.validate()) Button(self, text='...', width=2, padding=0, command=self.select_file).grid(row=1, column=3, sticky='sn', pady=4) Button(self, text='Ok', command=self.validate).grid(row=2, column=1, padx=4, pady=4, sticky='e') Button(self, text='Cancel', command=self.destroy).grid(row=2, column=2, padx=4, pady=4, sticky='w') self.transient(master) self.entry.focus_set() self.update_idletasks() self.grab_set() def validate(self): self.selected_kernel = self.entry.get() self.destroy() def select_file(self): filename = askopenfilename(self, "Select connection file", initialdir=jupyter_runtime_dir(), defaultextension='.json', filetypes=[('JSON', '*.json'), ('All files', '*')]) if filename: self.entry.delete(0, 'end') self.entry.insert(0, filename)
def concrete_inc_dec(cls, entry: ttk.Entry, operand: str): """Concrete function to add or substract the value from an input""" old_value = entry.get().split()[0] value = float(old_value) if operand == "+": value += 1 else: value -= 1 if value < 0: value = 0 if value == int(value): value = int(value) entry["state"] = "normal" entry.delete(0, "end") entry.insert(0, value) entry["state"] = "readonly"
class ArgEntry(Frame): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__namelabel = Label(self) self.__namelabel.pack(expand='yes', fill='x') self.__entry = Entry(self) self.__entry.pack(side='left', expand='yes', fill='x') self.__btn = Button(self) self.__btn.pack(side='right') self.__btn['state'] = 'disabled' @property def entry(self): return self.__entry @property def entry_text(self): return self.__entry.get() @entry_text.setter def entry_text(self, value): self.__entry.delete(0, 'end') self.__entry.insert(0, value) @property def button(self): return self.__btn @property def arg_name(self): return self.__namelabel['text'] @arg_name.setter def arg_name(self, val): self.__namelabel['text'] = val @property def arg_value(self): return self.__entry.get() @arg_value.setter def arg_value(self, val): self.__entry.delete(0, 'end') self.__entry.insert(0, val)
def escrever_mascara_entry(evento_char, ref_entry: ttk.Entry, mascara: str, especial: str = ''): """ Método estático para formatar a entrada de um Entry de acordo com mascara. Pode ser usado sem instanciação. :param evento_char: event.char do evento. :param ref_entry: Referência do componente Entry. :param mascara: Mascara base para a formatação. Deve apresentar espacos onde quer ser inseridos os números. :return: none. """ # Apaga o Entry caso o conteúdo seja o default. if ref_entry.get() == mascara: ref_entry.delete(0, 'end') # O event.char captura de tecla literalmente, mas não captura teclas de controle(Backspace, ...). # O event.keysym usa uma nomenclatura diferente, e captura todas as teclas. # Tkinter 8.5 reference gui for Python. 161-162. if evento_char in string.digits or evento_char in especial: pos = len(ref_entry.get()) try: if mascara[pos] != ' ': ref_entry.insert('end', mascara[pos]) except IndexError: ref_entry.delete(0, 'end')
class PasswordGenerator(Frame): def __init__(self): super().__init__() self.initUI() self.my_pw = '' def initUI(self): self.master.title('Simple PasswordGenerator v1.0') self.pack(fill=BOTH, expand=False) frame1 = Frame(self) frame1.pack(fill=X) lbl1 = Label(frame1, text='Input password lenght: (6-15)', width=27) lbl1.pack(side=LEFT, padx=5, pady=10) self.entry1 = Entry(frame1, width=10) self.entry1.pack(side=LEFT, padx=2) frame2 = Frame(self) frame2.pack(fill=X) lbl2 = Label(frame2, text='Generated password:'******'end') try: pw_length = int(self.entry1.get()) if pw_length >= 6 and pw_length <=15: for i in range(pw_length): index = random.randrange(len(possible_chars)) self.my_pw += possible_chars[index] self.entry2.insert(1, self.my_pw) self.my_pw = '' else: messagebox.showwarning('Error!', 'Password must be 6-15 characters!') self.entry1.delete(0, 'end') except: messagebox.showwarning('Error!', 'Field must contain a number!') self.entry1.delete(0, 'end')
def initui(self): self.nombre = Nombre(0,10) self.master.title("convertisseur") self.pack(fill=BOTH, expand=1) frame1 = Frame(self, relief = RAISED, borderwidth = 1) frame1.pack(fill = BOTH) label1 = Label(frame1, text = "Valeur") label1.pack(side = LEFT) self.valeur = StringVar() self.ecriture = StringVar() disp_valeur = Entry(frame1, textvar = self.valeur ) disp_valeur.delete(0, END) self.valeur.set(str(self.nombre.valeur)) # disp_valeur.insert(0, self.nombre.valeur) disp_valeur.pack(side = RIGHT, expand = True) disp_valeur.bind("<Return>", self.update_valeur) frame2 = Frame(self, relief = RAISED, borderwidth = 1) frame2.pack(fill = BOTH) label2 = Label(frame2, text = "Base") self.base = StringVar() label2.pack(side = LEFT) disp_base = Entry(frame2, textvar = self.base) disp_base.delete(0, END) self.base.set(self.nombre.base) # disp_base.insert(0, self.nombre.base) disp_base.pack(side = RIGHT, expand=True) disp_base.bind("<Return>", self.update_base) frame3 = Frame(self, relief=RAISED, borderwidth=1) frame3.pack(fill=BOTH) self.ecriture = StringVar() label3 = Label(frame3, text="Ecriture") label3.pack(side=LEFT) disp_ecriture = Entry(frame3, textvar = self.ecriture) disp_ecriture.delete(0, END) self.ecriture.set(self.nombre.represente()) # disp_base.insert(0, self.nombre.represente()) disp_ecriture.pack(side=RIGHT, expand=True) disp_ecriture.bind("<Return>", self.update_ecriture)
class ClientSetUp(Frame): """ Dialog box that presents 3 inputs to the user: Username Server IP Port Extends the Frame class in tkinter. """ def __init__(self): super().__init__() self.name = '' self.ip = '' self.port = '' self.initUI() def initUI(self): """ Initialises the dialog box. The box will contain fields for the username, server ip and server port. A button is used to submit the data. """ self.master.title("Connection Details") self.pack(fill=BOTH, expand=True) # Prepare the username field frame1 = Frame(self) frame1.pack(fill=X) lbl1 = Label(frame1, text="Username", width=14) lbl1.pack(side=LEFT, padx=5, pady=10) self.entry1 = Entry(frame1, textvariable=self.name) self.entry1.pack(fill=X, padx=5, expand=True) # Prepare the server address field frame2 = Frame(self) frame2.pack(fill=X) lbl2 = Label(frame2, text="Server Address", width=14) lbl2.pack(side=LEFT, padx=5, pady=10) self.entry2 = Entry(frame2, textvariable=self.ip) self.entry2.pack(fill=X, padx=5, expand=True) # Prepare the server port field frame3 = Frame(self) frame3.pack(fill=X) lbl3 = Label(frame3, text="Server Port", width=14) lbl3.pack(side=LEFT, padx=5, pady=10) self.entry3 = Entry(frame3, textvariable=self.port) self.entry3.pack(fill=X, padx=5, expand=True) frame4 = Frame(self) frame4.pack(fill=X) # Command tells the form what to do when the button is clicked btn = Button(frame4, text="Submit", command=self.onSubmit) btn.pack(padx=5, pady=10) # Give entry1 focus self.entry1.focus() # Set up what happens when Return is pressed # All entries will move onto the next except port which will submit # Note: These didn't work if they weren't lambdas (don't know why) self.entry1.bind('<Return>', lambda event: self.entry2.focus()) self.entry2.bind('<Return>', lambda event: self.entry3.focus()) self.entry3.bind('<Return>', lambda event: self.onSubmit()) photo = PhotoImage(file=resource_path('icon.png')) self.master.iconphoto(False, photo) def onSubmit(self): """ When clicked, the user input is stored in the instance variables and the boxes are cleared. The widget is then destroyed. """ self.name = self.entry1.get() self.ip = self.entry2.get() self.port = self.entry3.get() self.entry1.delete(0, END) self.entry2.delete(0, END) self.entry3.delete(0, END) self.quit() def retry(self, name='', ip='', port=''): """ Used if the user enters incorrect data. It will repopulate the fields where the data was correct. The mainloop is started once the fields are populated. Parameters: name (str): Name of client ip (str): IP of server port (str): Port of server """ self.entry1.insert(0, name) self.entry2.insert(0, ip) self.entry3.insert(0, port) self.mainloop() self.quit() def on_close(self): self.destroy() sys.exit()
class DaxModelParameter(): '''The UI elements and logic to set model parameter values. For this application; all model parameters are assumed to be floats (or ints cast to floats). Strings and Logicals need not apply. ''' def __init__(self, parent, label_frame, sherpa_model_parameter): '''Create model parameter UI element''' self.sherpa_par = sherpa_model_parameter self.parent = parent self.label_frame = label_frame self.initial_value = {'val': self.sherpa_par.val, 'min': self.sherpa_par.min, 'max': self.sherpa_par.max} self.render_ui() def _freeze_thaw(self): '''ACTION: set the freeze() or thaw() based on the checkbox value.''' if 1 == self.fz_box.get(): self.sherpa_par.freeze() else: self.sherpa_par.thaw() @staticmethod def __format_val(val): 'Format parameter values' retval = "{:.5g}".format(val) return retval def reset(self): """Reset values to original""" for field in ['max', 'min', 'val']: to_mod = getattr(self, field) to_mod.delete(0, END) to_mod.insert(0, self.__format_val(self.initial_value[field])) to_mod.configure(foreground="black") setattr(self.sherpa_par, field, self.initial_value[field]) def update(self): """Reset values to original""" for field in ['max', 'min', 'val']: to_mod = getattr(self, field) newval = getattr(self.sherpa_par, field) to_mod.delete(0, END) to_mod.insert(0, self.__format_val(newval)) # ~ to_mod.configure(foreground="black") # ~ setattr(self.sherpa_par, field, self.initial_value[field]) def entry_callback(self, keyevt, field): '''ACTION: set the model parameter value when the user type <<Return>>. Otherwise, when user edits value it turns red so user knows it hasn't been set yet. All values are cast|set to doubles. There is no validation in the UI against the min|max values. Sherpa raises an exception if you try to go beyond the limits so the color remains red until valid value is entered. ''' from sherpa.utils.err import ParameterErr # Note: use .char instead of .keysym because Return # and Enter on the keypad are different keysym's but both # generate CR. This makes sense since can remap keyboard # keys -- the action we want is CR, whichever key generates it. # Update: Unfortunately the .char field is cleared # in newer versions of python in the KeyRelease callback, and # the Key callback doesn't work (order of callback doesn't change # text color correctly). So, I'm back to using the keysym. to_mod = getattr(self, field) if keyevt.keysym in ['Return', 'KP_Enter', 'Enter']: try: fval = float(to_mod.get()) setattr(self.sherpa_par, field, fval) to_mod.configure(foreground="black") to_mod.last_value = to_mod.get() except (ValueError, ParameterErr) as val_err: messagebox.showerror("DAX Model Editor", str(val_err)) else: if to_mod.get() != to_mod.last_value: to_mod.configure(foreground="red") def render_ui(self): '''Render the parameter UI elements and attach bindings''' row = self.parent.get_row() win = self.label_frame # The parameter name lab = Label(win, text=self.sherpa_par.name, width=12, anchor="e") lab.grid(row=row, column=0, padx=(5, 5), pady=2) # The current parameter value self.val_str = StringVar() self.val = Entry(win, textvariable=self.val_str, foreground="black", width=12, justify="right") self.val.grid(row=row, column=1, padx=(5, 5), pady=2) self.val.delete(0, END) self.val.insert(0, self.__format_val(self.sherpa_par.val)) self.val.last_value = self.val.get() self.val.bind("<KeyRelease>", lambda x: self.entry_callback(x, field='val')) # Frozen|Thawed checkbox. Checked if frozen. self.fz_box = IntVar() if self.sherpa_par.frozen is True: self.fz_box.set(1) else: self.fz_box.set(0) fzbtn = Checkbutton(win, text="", variable=self.fz_box, command=self._freeze_thaw) fzbtn.grid(row=row, column=2, padx=(5, 5), pady=2) # The min value self.min_str = StringVar() self.min = Entry(win, textvariable=self.min_str, foreground="black", width=12, justify="right") self.min.grid(row=row, column=3, padx=(5, 5), pady=2) self.min.delete(0, END) self.min.insert(0, self.__format_val(self.sherpa_par.min)) self.min.last_value = self.min.get() self.min.bind("<KeyRelease>", lambda x: self.entry_callback(x, field='min')) # The max value self.max_str = StringVar() self.max = Entry(win, textvariable=self.max_str, foreground="black", width=12, justify="right") self.max.grid(row=row, column=4, padx=(5, 5), pady=2) self.max.delete(0, END) self.max.insert(0, self.__format_val(self.sherpa_par.max)) self.max.last_value = self.max.get() self.max.bind("<KeyRelease>", lambda x: self.entry_callback(x, field='max')) # The units of the parameter par_units = Label(win, text="{}".format(self.sherpa_par.units), width=20, anchor="e") par_units.grid(row=row, column=5, padx=(5, 5), pady=2)
class loginPage(Frame): ''' This is a frame class and thus it implements Frame, it also contains the required showItems and hideItems function which I believe are present in each frame class tomake things a bit simpler ''' def __init__(self, parent, main, **kw): Frame.__init__(self, parent, **kw) self.main = main self.label_head = Label(text='Welcome to the Stock Exchange', font=LARGE_FONT) self.l_user = Label(text='Username') self.user = Entry() self.l_pass = Label(text='Password') self.password = Entry(show='*') self.login_back_b = Button(text='Login', command=lambda: self.login_check()) self.sign_up_b = Button( text='Sign Up', command=lambda: self.main.show_frame(SIGN_UP_PAGE, LOGIN_PAGE)) self.view_stock_b = Button( text='View Stock', command=lambda: self.main.show_frame(VIEW_STOCK, LOGIN_PAGE)) self.main.bind('<Return>', self.keyPress) def showItems(self, main): self.label_head.grid(column=0, row=0, columnspan=2) self.l_user.grid(column=0, row=1) self.user.grid(column=1, row=1) self.l_pass.grid(column=0, row=2) self.password.grid(column=1, row=2) self.login_back_b.grid(column=0, row=3, columnspan=2) self.sign_up_b.grid(column=0, row=4) self.view_stock_b.grid(column=1, row=4) def hideItems(self, main): self.label_head.grid_forget() self.l_user.grid_forget() self.user.grid_forget() self.l_pass.grid_forget() self.password.grid_forget() self.login_back_b.grid_forget() self.sign_up_b.grid_forget() self.view_stock_b.grid_forget() def keyPress(self, event): ''' this is the binding fucntion for any keyPress event ''' if event.keysym == 'Return': self.login_check() def login_check(self): ''' This function checks whether a given user is registered on the system or not then continues to chech whether they have entered the correct password. it also creates messagesboxes to give the proper error message to the user ''' main = self.main user = str(self.user.get()) password = bytes(str(self.password.get()), 'utf-8') main.login = False check1 = user in main.pass_dict check3 = user == 'admin' and password == 'admin' if check1: check2 = main.pass_dict[user] == password if check1 and check2: main.login = True else: main.login = False if check3: main.login = True main.admin = True main.show_frame(ADMIN_PAGE, LOGIN_PAGE) if main.login: if not check3: main.present_user, main.p_user_dict = user, main.users_dict[ user] main.show_frame(VIEW_STOCK, LOGIN_PAGE) self.password.delete(0, END) self.user.delete(0, END) else: self.password.delete(0, END) showinfo(message='Incorrect Username or Password entered')
class retailPage(Frame): ''' the main page that actually branches into 2 seperate frames for buying and selling this frame provides the use with the ''' def __init__(self, parent, main, **kw): Frame.__init__(self, parent, **kw) self.main = main self.Head = Label(font=MED_FONT) self.retail_back_b = Button(text='BACK', command=self.back_fb) self.stock_name = StringVar() self.stock_name_ = '' self.stock_name_c = Combobox(textvariable=self.stock_name) self.stock_name_l = Label(text='Stock name: ') self.amount = Entry() self.amount_l = Label(text='Number of stocks :') self.check_avail_b = Button(text='Check') self.cost_l = Label(text='Cost: ') self.cost = Label() self.buy_stock_b = Button(text='BUY Stock') self.sellp_l = Label(text='Current Selling Price: ') self.profit_l = Label(text='PROFIT/LOSS') self.sell_stock_b = Button(text='SELL Stock') self.page = None def showItems(self, main): self.Head.grid(column=0, row=0, columnspan=2) self.Head.configure(text='logged in as %s' % main.present_user) if self.page == 'b': self.buy() elif self.page == 's': self.sell() def hideItems(self, main): self.Head.grid_forget() def buy(self): '''This function creates the buy window ''' self.retail_back_b.grid(column=1, row=5) self.retail_back_b.config(command=self.back_fb) self.stock_name_l.grid(column=0, row=1) self.stock_name_c.grid(column=1, row=1) self.stock_name_c.config(values=self.main.shares_dict.keys()) self.amount_l.grid(column=0, row=2) self.amount.grid(column=1, row=2) self.check_avail_b.config(command=self.check_avail_buy) self.check_avail_b.grid(column=0, row=3) self.cost_l.grid(column=0, row=4, columnspan=2) self.buy_stock_b.config(command=self.buy_stock, text='Buy Stock', state='disabled') self.buy_stock_b.grid(column=0, row=5) def sell(self): '''This function creats the sell window''' self.retail_back_b.grid(column=1, row=6) self.retail_back_b.config(command=self.back_fs) self.check_avail_b.config(command=self.check_avail_sell) self.stock_name_l.grid(column=0, row=1) self.stock_name_c.grid(column=1, row=1) self.stock_name_c.config(values=self.main.shares_dict.keys()) self.amount_l.grid(column=0, row=2) self.amount.grid(column=1, row=2) self.check_avail_b.grid(column=0, row=3) self.sellp_l.grid(column=0, row=4, columnspan=2) self.profit_l.grid(column=0, row=5, columnspan=2) self.sell_stock_b.config(command=self.sell_stock, state='disabled', text='Check') self.sell_stock_b.grid(column=0, row=6) def back_fb(self): '''Back from buy i.e. removes all the items needed to make buy''' self.retail_back_b.grid_forget() self.Head.grid(column=0, row=0, columnspan=2) self.stock_name_l.grid_forget() self.stock_name_c.grid_forget() self.amount_l.grid_forget() self.amount.grid_forget() self.check_avail_b.grid_forget() self.cost_l.grid_forget() self.buy_stock_b.grid_forget() self.buy_stock_b.config(state='disabled') self.main.show_frame(VIEW_STOCK, RETAIL_PAGE) self.stock_name.set('') self.amount.delete(0, END) def back_fs(self): ''' Back from sell i.e. removes all the items needed to make it sell window''' self.Head.grid(column=0, row=0, columnspan=2) self.retail_back_b.grid_forget() self.check_avail_b.grid_forget() self.stock_name_l.grid_forget() self.stock_name_c.grid_forget() self.amount_l.grid_forget() self.amount.grid_forget() self.sellp_l.grid_forget() self.profit_l.grid_forget() self.sell_stock_b.grid_forget() self.main.show_frame(VIEW_STOCK, RETAIL_PAGE) self.stock_name.set('') self.amount.delete(0, END) self.check_avail_b.grid_forget() def check_avail_buy(self): ''' Performs a check whether the number of shares requisted are available or not and then check whether the person has the required amounts of fund or not for the transaction to go through''' name = self.stock_name.get() l2 = self.main.accounts[self.main.present_user] if name in self.main.shares_dict.keys(): li = self.main.shares_dict[name] else: self.stock_name.delete(0, END) showinfo(meassage='Enter a Valid Stock name') available_num = int(li['tot_amount']) - int(li['tot_sold']) req = int(self.amount.get()) cost = req * int(li['cost']) if req < 0: showinfo(message='Enter a Valid amount') elif req > available_num: showinfo(message='Enter an amount less than ' + str(available_num)) elif cost > int(l2['balance']): showinfo(message='You have only %s in you account' % l2['balance']) else: self.cost_l.config(text='Cost: \t' + li['cost'] + '*' + str(req) + '=' + str(cost)) self.buy_stock_b.config(state='normal') def check_avail_sell(self): ''' Performs a check whether the user has enough stocks to sell''' name = self.stock_name.get() if name in self.main.shares_dict.keys(): li = self.main.shares_dict[name] else: self.stock_name.delete(0, END) showinfo(message='Enter a Valid Stock name') req = int(self.amount.get()) if req < 0: showinfo(message='Please Enter a Valid amount') self.amount.delete(0, END) li = self.main.p_user_dict ok = False for i in li: if name == i['name']: ok = True buff = i if req > int(buff['tot_owned']): showinfo(message='You dont have that many stocks try less than ' + buff['tot_owned']) self.amount.delete(0, END) cost = self.main.shares_dict[name]['cost'] tot_cost = req * int(cost) try: spent = req * float(buff['money_spent']) / int(buff['tot_owned']) except: spent = 0 pol = tot_cost - spent if pol >= 0: self.profit_l.config() elif pol < 0: self.profit_l.config() if req <= int(buff['tot_owned']): self.sellp_l.config(text='Current Selling Price: \t' + cost) self.profit_l.config(text="PROFIT-LOSS: \t" + str(pol)) self.sell_stock_b.config(command=self.sell_stock, text='Sell Stock') showinfo(message= 'Everthing is ok, \nClicking Sell will execute the trade') self.sell_stock_b.config(state='normal') def buy_stock(self): '''Finally Executes the transaction and asks the user for conformation one last time''' name = self.stock_name.get() li = self.main.shares_dict[name] for i in range(len(self.main.p_user_dict)): if name == self.main.p_user_dict[i]['name']: index = i req = int(self.amount.get()) tot_cost = req * int(li['cost']) self.main.shares_dict[name]['tot_sold'] = str( int(self.main.shares_dict[name]['tot_sold']) + req) self.main.p_user_dict[index]['tot_owned'] = str( int(self.main.p_user_dict[index]['tot_owned']) + req) self.main.p_user_dict[index]['money_spent'] = str( int(self.main.p_user_dict[index]['money_spent']) + tot_cost) self.main.users_dict[ self.main.present_user][index] = self.main.p_user_dict[index] balance = int(self.main.accounts[self.main.present_user]['balance']) self.main.accounts[self.main.present_user]['balance'] = str(balance - tot_cost) self.cost_l.config(text='Cost: ') showinfo( message='You have just bought %s of the stock %s at the price %s' % (str(req), name, str(tot_cost))) self.stock_name.set('') self.main.show_frame(VIEW_STOCK, RETAIL_PAGE) self.back_fb() def sell_stock(self): '''Asks the user for conformation and then completes the transaction of selling, at this point the profit field is also updated''' name = self.stock_name.get() req = int(self.amount.get()) li = self.main.p_user_dict for i in range(len(li)): if name == li[i]['name']: ok = True buff = i tot_cost = req * int(self.main.shares_dict[name]['cost']) try: spent = req * float( self.main.p_user_dict[buff]['money_spent']) / int( self.main.p_user_dict[buff]['tot_owned']) except ZeroDivisionError: spent = 0 self.main.shares_dict[name]['tot_sold'] = str( int(self.main.shares_dict[name]['tot_sold']) - req) self.main.p_user_dict[buff]['tot_owned'] = str( int(self.main.p_user_dict[buff]['tot_owned']) - req) pol = tot_cost - spent diff = int(self.main.p_user_dict[buff]['money_spent']) - tot_cost self.main.p_user_dict[buff]['money_spent'] = str( diff) if diff > 0 else '0' self.main.users_dict[self.main.present_user] = self.main.p_user_dict profit = int(self.main.accounts[self.main.present_user]['profit']) self.main.accounts[self.main.present_user]['profit'] = str( int(profit + pol)) balance = int(self.main.accounts[self.main.present_user]['balance']) self.main.accounts[self.main.present_user]['balance'] = str(balance + tot_cost) self.retail_back_b.grid_forget() self.profit_l.config(text='PROFIT/LOSS: ') self.sellp_l.config(text='Current selling price: ') self.main.show_frame(VIEW_STOCK, RETAIL_PAGE) self.back_fs()
class MainView(Frame): def __init__(self, root, model: Model): super().__init__(root) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) self.model = model self.__init_components() self.grid(sticky='nsew') self.__bind_action_events() def __init_components(self): # Instanciar widgets self.panel_form = Labelframe(self, text='Tarea') self.panel_tasks = Labelframe(self, text='Tareas por hacer') self.panel_complete_tasks = Labelframe(self, text='Tareas completas') self.label_name = Label(self.panel_form, text='Nombre:') self.label_description = Label(self.panel_form, text='Descripción:') self.entry_name = Entry(self.panel_form) self.entry_description = Entry(self.panel_form) self.btn_modify_task = Button(self.panel_form, text='Editar tarea', state=DISABLED, command=self.__modify_task) self.btn_new_task = Button(self.panel_form, text='Nueva tarea', command=self.__new_task) self.btn_delete_task = Button(self.panel_form, text='Eliminar tarea', state=DISABLED, command=self.__delete_task) self.btn_clear_form = Button(self.panel_form, text='Limpiar campos', command=self.__clear_form) self.btn_complete_task = Button(self.panel_form, text='Completar tarea', state=DISABLED, command=self.__complete_task) self.scroll_tasks = Scrollbar(self.panel_tasks, orient=VERTICAL) self.scroll_complete_tasks = Scrollbar(self.panel_complete_tasks, orient=VERTICAL) self.list_tasks = Listbox(self.panel_tasks, selectmode=SINGLE, height=10, width=25, yscrollcommand=self.scroll_tasks.set) self.list_complete_tasks = Listbox( self.panel_complete_tasks, selectmode=SINGLE, height=10, width=25, yscrollcommand=self.scroll_complete_tasks.set) # Posicionar los widgets # Panel de formulario de tareas self.panel_form.pack(fill='both', expand='yes', padx=10, pady=5, ipadx=5, ipady=5) self.panel_form.columnconfigure(0, weight=1) self.panel_form.rowconfigure(0, weight=1) self.label_name.grid(row=0, column=0, padx=5, sticky='w') self.entry_name.grid(row=0, column=1, padx=5, sticky='w') self.label_description.grid(row=1, column=0, padx=5, pady=5, sticky='w') self.entry_description.grid(row=1, column=1, padx=5, pady=10, sticky='w') # Botones self.btn_modify_task.grid(row=2, column=0, ipady=4, sticky='we') self.btn_new_task.grid(row=2, column=1, ipady=4, sticky='we') self.btn_delete_task.grid(row=3, column=0, ipady=4, sticky='we') self.btn_clear_form.grid(row=3, column=1, ipady=4, sticky='we') self.btn_complete_task.grid(row=4, column=0, columnspan=2, ipady=4, sticky='we') config_list = { 'fill': 'both', 'expand': 'yes', 'padx': 10, 'pady': 5, 'ipadx': 5, 'ipady': 5 } # Panel de lista de tareas pendientes self.panel_tasks.pack(config_list) self.panel_tasks.columnconfigure(0, weight=20) self.panel_tasks.columnconfigure(1, weight=1) self.list_tasks.grid(row=0, column=0, sticky='we') self.scroll_tasks.configure(command=self.list_tasks.yview) self.scroll_tasks.grid(row=0, column=1, sticky='ns') # Panel de lista de tareas completas self.panel_complete_tasks.pack(config_list) self.panel_complete_tasks.columnconfigure(0, weight=20) self.panel_complete_tasks.columnconfigure(1, weight=1) self.list_complete_tasks.grid(row=0, column=0, sticky='we') self.scroll_complete_tasks.configure( command=self.list_complete_tasks.yview) self.scroll_complete_tasks.grid(row=0, column=1, sticky='ns') def __bind_action_events(self): # self.btn_new_task.bind('<Button>', self.__new_task) # self.btn_modify_task.bind('<Button>', self.__modify_task) # self.btn_delete_task.bind('<Button>', self.__delete_task) # self.btn_complete_task.bind('<Button>', self.__complete_task) self.list_tasks.bind('<<ListboxSelect>>', self.__select_task) def __new_task(self): log_info('Botón {} pulsado'.format( self.btn_new_task.config('text')[-1])) name_value = self.entry_name.get() description_value = self.entry_description.get() if name_value: self.model.new_task(name_value, description_value) self.update_tables() else: messagebox.showwarning('AVISO', 'Complete el campo de nombre') def __modify_task(self): index = self.__selected_task() name_value = self.entry_name.get() description_value = self.entry_description.get() if index != -1: task = self.model.get_task(self.model.tasks[index].get_id()) complete = self.model.modify_task(task.get_id(), name_value, description_value) if complete: messagebox.showinfo('Aviso', 'Tarea: {} editada'.format(task.name)) self.update_tables() def __delete_task(self): index = self.__selected_task() if index != -1: task = self.model.get_task(self.model.tasks[index].get_id()) self.model.delete_task(task.get_id()) self.update_tables() def __complete_task(self): index = self.__selected_task() if index != -1: task = self.model.get_task(self.model.tasks[index].get_id()) task.close_todo() complete = self.model.modify_task(task.get_id(), task.name, task.description) if complete: messagebox.showinfo('Aviso', 'Tarea: {} completa'.format(task.name)) self.update_tables() def __clear_form(self): self.clear_form() self.__change_state_btn(DISABLED) def __select_task(self, event): self.clear_form() index = self.__selected_task() if index != -1: task = self.model.get_task(self.model.tasks[index].get_id()) self.set_form(task) self.__change_state_btn(NORMAL) def __change_state_btn(self, state: str): self.btn_new_task.config( state=NORMAL if state == DISABLED else DISABLED) self.btn_delete_task.config(state=state) self.btn_modify_task.config(state=state) self.btn_complete_task.config(state=state) def __selected_task(self): try: return self.list_tasks.curselection()[0] except IndexError: self.__change_state_btn(DISABLED) self.list_complete_tasks.activate(-1) return -1 def update_tables(self): log_info('Actualizando tablas') self.list_tasks.delete(0, END) self.list_complete_tasks.delete(0, END) for index in range(len(self.model.tasks)): self.list_tasks.insert(index, self.model.tasks[index].name) for index in range(len(self.model.complete_tasks)): self.list_complete_tasks.insert( index, self.model.complete_tasks[index].name) def clear_form(self): self.entry_name.delete(0, END) self.entry_description.delete(0, END) def set_form(self, task: Task): if task is not None: self.entry_name.insert(0, task.name) self.entry_description.insert(0, task.description) else: log_error('No se encontró la tarea seleccionada')
class MultipleABFEditor(Toplevel): def __init__(self, parent, paths, dispatch_event): """ Initialises a window for loading datasets. First loads a datafile. After waiting for this process, creates a window to select different channels and specify the unit of the data. """ super().__init__(None) self.parent = parent self.dispatch_event = dispatch_event self.title("ABF data set") self.editor = None self.paths = paths self.paths_new = [] if not test1.alpha: self.path = paths[0] self.abf = ABF(self.path) self.values = None self.values1 = None self.resizable(width=False, height=False) waiting_popup = Toplevel(parent) waiting_popup.attributes("-topmost", True) waiting_label = Label(waiting_popup, text="Preparing data...") waiting_label.pack() waiting_popup.update() waiting_popup.destroy() try: if not test1.alpha: self.attributes("-topmost", True) dividends = self.abf._abf[self.abf._abf.names.index("channelNames")] units = self.abf._abf[self.abf._abf.names.index("channelUnits")] for i in range(len(self.abf._abf[self.abf._abf.names.index("channelNames")])): dividends[i] = dividends[i] + " " + \ self.abf._abf[self.abf._abf.names.index("channelUnits")][i] divisors = dividends + 'None' self.dividend_var = StringVar(self) # usually contains current self.divisor_var = StringVar(self) # usually contains voltage self.dividend_var.set(dividends[0]) self.divisor_var.set(divisors[1]) self.dividends = dividends self.units = units self.divisors = divisors path_lab = Label(self, text="Path: {}".format(self.paths[0])) path_lab.pack(padx=10, pady=10) channel_selector = Frame(self) warning = Label(channel_selector, text="Select a channel, optionally divide it by another channel") dividend_menu = OptionMenu(channel_selector, self.dividend_var, *dividends) divisor_menu = OptionMenu(channel_selector, self.divisor_var, *divisors) warning.pack() dividend_menu.pack() divisor_menu.pack() channel_selector.pack(padx=10, pady=10) unit_selector = Frame(self) unit_lab = Label(unit_selector, text="Unit: ") unit_lab.pack(side="left", fill="x") self.unit = Entry(unit_selector) self.unit.delete(0, "end") self.unit.insert(0, "nS") self.unit.pack(side="left", fill="x") unit_selector.pack(padx=10, pady=10) self.ok = Button(self, text="Load", command=self.load) self.ok.pack(side="left", fill="x", padx=10, pady=10) self.ok_all = Button(self, text="Load All", command=self.load_all) self.ok_all.pack() self.ok_all.place(relx=0.5, rely=0.88, anchor=CENTER) cancel = Button(self, text="Don't load", command=self.destroy) cancel.pack(side="right", fill="x", padx=10, pady=10) self.bind('<Return>', self.load) except: messagebox.showwarning( "Error loading file", self.paths[0] + " could not be loaded.") self.destroy() @property def params(self): """ Parameters which will be given to the controller. """ dividend = self.dividend_var.get() # print(dividend) divisor = self.divisor_var.get() # print(divisor) channels = [self.dividends.index(dividend) + 1] # print("Channels", channels) if divisor != 'None': channels += [self.divisors.index(divisor) + 1] return dict(sweep=1, channels=channels, unit=self.unit.get()) def load(self, test=None): """ Gives the dataset and parameters to the controller, then destroys the editor. """ self.ok.config(state = DISABLED) self.ok_all.config(state=DISABLED) if len(self.paths) > 0: del self.paths[0] self.dispatch_event("add_abf_dataset", self.abf, self.params) if len(self.paths) != 0: test1.alpha = False Editor = MultipleABFEditor self.editor = Editor(self.parent, self.paths, self.dispatch_event) else: test1.alpha = True self.destroy() def load_all(self, test=None): """ Gives the datasets with same channel names , channel units , same number of channels and parameters( same parameters to all data sets with same channel names , channel units , same number of channels )to the controller, then destroys the editor. """ self.ok_all.config(state = DISABLED) self.ok.config(state=DISABLED) self.values = ABF.names1(self.abf) values = set(self.values) self.values1 = ABF.units1(self.abf) values1 = set(self.values1) params1 = self.params for path in self.paths: self.abf = ABF(path) dividends = self.abf._abf[self.abf._abf.names.index("channelNames")] for i in range(len(self.abf._abf[self.abf._abf.names.index("channelNames")])): dividends[i] = dividends[i] + " " + \ self.abf._abf[self.abf._abf.names.index("channelUnits")][i] units = self.abf._abf[self.abf._abf.names.index("channelUnits")] dividends = set(dividends) units = set(units) if len(self.values) == len(dividends) and len(self.values1) == len(units) and (values == dividends) and ( values1 == units): self.dispatch_event("add_abf_dataset", self.abf, params1) else: self.paths_new.append(path) if len(self.paths_new) != 0: test1.alpha = False Editor = MultipleABFEditor self.editor = Editor(self.parent, self.paths_new, self.dispatch_event) else: test1.alpha = True self.destroy()
class MarkovDemo(Frame): "MarkovDemo(master=None, **kw) -> MarkovDemo instance" TEXT = dict(height=2, width=46, wrap=WORD) # Text Options GRID = dict(padx=5, pady=5) # Grid Options # Initialize a MarkovDemo instance with a GUI for interaction. def __init__(self, master=None, **kw): "Initialize the MarkovDemo instance's widgets and settings." super().__init__(master, **kw) self.build_widgets() self.place_widgets() self.setup_widgets() self.grid_rowconfigure(2, weight=1) self.grid_rowconfigure(3, weight=1) self.grid_columnconfigure(0, weight=1) self.key = self.primer = None def build_widgets(self): "Build the various widgets that will be used in the program." # Create processing frame widgets. self.processing_frame = LabelFrame(self, text='Processing Mode:') self.mode_var = StringVar(self, 'encode') self.decode_button = Radiobutton(self.processing_frame, text='Decode Cipher-Text', command=self.handle_radiobuttons, value='decode', variable=self.mode_var) self.encode_button = Radiobutton(self.processing_frame, text='Encode Plain-Text', command=self.handle_radiobuttons, value='encode', variable=self.mode_var) self.freeze_var = BooleanVar(self, False) self.freeze_button = Checkbutton(self.processing_frame, text='Freeze Key & Primer', command=self.handle_checkbutton, offvalue=False, onvalue=True, variable=self.freeze_var) # Create encoding frame widgets. self.encoding_frame = LabelFrame(self, text='Encoding Options:') self.chain_size_label = Label(self.encoding_frame, text='Chain Size:') self.chain_size_entry = Entry(self.encoding_frame) self.plain_text_label = Label(self.encoding_frame, text='Plain-Text:') self.plain_text_entry = Entry(self.encoding_frame) # Create input frame widgets. self.input_frame = LabelFrame(self, text='Input Area:') self.input_text = ScrolledText(self.input_frame, **self.TEXT) # Create output frame widgets. self.output_frame = LabelFrame(self, text='Output Area:') self.output_text = ScrolledText(self.output_frame, **self.TEXT) def place_widgets(self): "Place the widgets where they belong in the MarkovDemo frame." # Locate processing frame widgets. self.processing_frame.grid(sticky=EW, **self.GRID) self.decode_button.grid(row=0, column=0, **self.GRID) self.encode_button.grid(row=0, column=1, **self.GRID) self.freeze_button.grid(row=0, column=2, **self.GRID) # Locate encoding frame widgets. self.encoding_frame.grid(sticky=EW, **self.GRID) self.chain_size_label.grid(row=0, column=0, sticky=W, **self.GRID) self.chain_size_entry.grid(row=0, column=1, sticky=EW, **self.GRID) self.plain_text_label.grid(row=1, column=0, sticky=W, **self.GRID) self.plain_text_entry.grid(row=1, column=1, sticky=EW, **self.GRID) self.encoding_frame.grid_columnconfigure(1, weight=1) # Locate input frame widgets. self.input_frame.grid(sticky=NSEW, **self.GRID) self.input_text.grid(sticky=NSEW, **self.GRID) self.input_frame.grid_rowconfigure(0, weight=1) self.input_frame.grid_columnconfigure(0, weight=1) # Locate output frame widgets. self.output_frame.grid(sticky=NSEW, **self.GRID) self.output_text.grid(sticky=NSEW, **self.GRID) self.output_frame.grid_rowconfigure(0, weight=1) self.output_frame.grid_columnconfigure(0, weight=1) def setup_widgets(self): "Setup each widget's configuration for the events they handle." self.input_text.bind('<Key>', self.handle_key_events) self.input_text.bind('<Control-Key-a>', self.handle_control_a) self.input_text.bind('<Control-Key-/>', lambda event: 'break') self.output_text['state'] = DISABLED self.output_text.bind('<Control-Key-a>', self.handle_control_a) self.output_text.bind('<Control-Key-/>', lambda event: 'break') ######################################################################## # Take care of any special event needing dedicated processing. def handle_radiobuttons(self): "Change the interface based on the encoding / decoding setting." if self.encrypting: self.freeze_button.grid() if not self.freeze_var.get(): self.encoding_frame.grid() else: self.freeze_button.grid_remove() if not self.freeze_var.get(): self.encoding_frame.grid_remove() self.handle_key_events(None) def handle_checkbutton(self): "Change the interface based on the key / primer freeze setting." if self.freeze_var.get(): self.encoding_frame.grid_remove() else: self.encoding_frame.grid() def handle_key_events(self, event): "Schedule refreshing the output area after an input area event." if event is None or event.char and event.state | 0o11 == 0o11: self.after_idle(self.refresh) @staticmethod def handle_control_a(event): "Select all text in the widget associated with the given event." event.widget.tag_add(SEL, 1.0, END + '-1c') return 'break' ######################################################################## # Handle interface's updates when either encoding or decoding. def refresh(self): "Refresh the output based on the value of the input." text = self.input_text.get(1.0, END + '-1c') if not text: self.output = text elif self.encrypting: self.encode(text) else: self.decode(text) def output(self, value): "Set the text in the output area to the string value." self.output_text['state'] = NORMAL self.output_text.delete(1.0, END) self.output_text.insert(END, value) if self.encrypting and self.freeze_var.get(): self.output_text.see(END) self.output_text['state'] = DISABLED output = property(fset=output, doc='Output area property.') @property def chain_size(self): "Chain size for the Markov chains used when encrypting." try: value = ast.literal_eval(self.chain_size_entry.get()) assert isinstance(value, int) and 2 <= value <= 256 return value except: self.chain_size_entry.delete(0, END) self.chain_size_entry.insert(0, '2') return 2 @property def plain_text(self): "Plain text or ignored characters in encryption process." try: value = self.repr_to_obj(self.plain_text_entry.get(), '') assert isinstance(value, str) return value except: self.plain_text_entry.delete(0, END) return '' ######################################################################## # Encrypt a string for display in the interface's output area. def encode(self, string): "Encode the string and show the cipher-text in the output." try: cipher = self.build_cipher(string) except ValueError: self.output = '' except: self.output = traceback.format_exc() else: self.output = self.build_header() + '\n\n' + cipher def build_cipher(self, string): "Build cipher-text based on plain-text and return answer." if self.key and self.freeze_var.get(): cipher, primer = me.encrypt_str(string, self.key, self.primer) else: args = string, self.chain_size, self.plain_text cipher, self.key, self.primer = me.auto_encrypt_str(*args) return cipher def build_header(self): "Build header from key and primer values in current use." header = '\n'.join(map(self.bytes_to_repr, self.key.data)) header += '\n' + self.bytes_to_repr(self.primer.data) return header ######################################################################## # Decrypt a string for display in the interface's output area. def decode(self, string): "Decode encrypted message and display plain-text in output." try: cipher = self.extract_keys(string) text = self.extract_text(cipher) except ValueError: self.output = '' except: self.output = traceback.format_exc() else: self.output = text def extract_keys(self, string): "Extract keys to decryption and return the cipher-text area." header, cipher = string.split('\n\n', 1) *key, primer = map(self.repr_to_obj, header.split('\n')) self.key, self.primer = me.Key(tuple(key)), me.Primer(primer) return cipher def extract_text(self, string): "Extract text message from string using built key and primer." text, primer = me.decrypt_str(string, self.key, self.primer) return text ######################################################################## # Provide some special methods to simplify the program's code. @property def encrypting(self): "Encrypting boolean stating current operations mode." return {'encode': True, 'decode': False}[self.mode_var.get()] @staticmethod def bytes_to_repr(obj): "Convert bytes object into suitable representation." if not isinstance(obj, bytes): raise TypeError('Object must be a bytes instance!') return repr(obj)[2:-1] @staticmethod def repr_to_obj(string, prefix='b'): "Convert representation into an equivalent object." for template in '{}"{}"', "{}'{}'": try: return ast.literal_eval(template.format(prefix, string)) except: pass raise ValueError('Cannot convert {!r} to object!'.format(string)) @classmethod def main(cls): "Create context for demo and run a test instance." NoDefaultRoot() root = Tk() root.minsize(420, 330) root.title('Markov Demo 2') test = cls(root) test.grid(sticky=NSEW) root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) root.mainloop()
class DateEntry(Frame): def __init__(self, master=None, dateformat='%B %d, %Y', firstweekday=6, startdate=None, **kw ): """ Args: master (Widget): The parent widget. dateformat (str): The format string used to render the text in the entry widget. Default is '%B %d, %Y'. firstweekday (int): Specifies the first day of the week. ``0`` is Monday, ``6`` is Sunday (the default). startdate (datetime): The date to be in focus when the calendar is displayed. Current date is default. **kw: Optional keyword arguments to be passed to containing frame widget. """ super().__init__(master=master, **kw) self.dateformat = dateformat self.firstweekday = firstweekday self.image = self.draw_button_image('white') self.startdate = startdate or datetime.today() # widget layout self.entry = Entry(self, name='date-entry') self.entry.pack(side='left', fill='x', expand='yes') self.button = ttk.Button(self, image=self.image, command=self.on_date_ask, padding=(2, 2)) self.button.pack(side='left') # TODO consider adding data validation: https://www.geeksforgeeks.org/python-tkinter-validating-entry-widget/ self.entry.insert('end', self.startdate.strftime(dateformat)) # starting entry value def draw_button_image(self, color): """Draw a calendar button image of the specified color Image reference: https://www.123rf.com/photo_117523637_stock-vector-modern-icon-calendar-button-applications.html Args: color (str): the color to draw the image foreground. Returns: PhotoImage: the image created for the calendar button. """ im = Image.new('RGBA', (21, 22)) draw = ImageDraw.Draw(im) # outline draw.rounded_rectangle([1, 3, 20, 21], radius=2, outline=color, width=1) # page spirals draw.rectangle([4, 1, 5, 5], fill=color) draw.rectangle([10, 1, 11, 5], fill=color) draw.rectangle([16, 1, 17, 5], fill=color) # row 1 draw.rectangle([7, 9, 9, 11], fill=color) draw.rectangle([11, 9, 13, 11], fill=color) draw.rectangle([15, 9, 17, 11], fill=color) # row 2 draw.rectangle([3, 13, 5, 15], fill=color) draw.rectangle([7, 13, 9, 15], fill=color) draw.rectangle([11, 13, 13, 15], fill=color) draw.rectangle([15, 13, 17, 15], fill=color) # row 3 draw.rectangle([3, 17, 5, 19], fill=color) draw.rectangle([7, 17, 9, 19], fill=color) draw.rectangle([11, 17, 13, 19], fill=color) return ImageTk.PhotoImage(im) def on_date_ask(self): """A callback for when a date is requested from the widget button. Try to grab the initial date from the entry if possible. However, if this date is note valid, use the current date and print a message to the console. """ try: self.startdate = datetime.strptime(self.entry.get(), self.dateformat) except Exception as e: print(e) self.startdate = datetime.today() olddate = datetime.strptime(self.entry.get() or self.startdate, self.dateformat) newdate = ask_date(self.entry, startdate=olddate, firstweekday=self.firstweekday) self.entry.delete('0', 'end') self.entry.insert('end', newdate.strftime(self.dateformat)) self.entry.focus_force()
class AudioDemo(Frame): """ A demo application class that provides simple GUI for testing featurizer+classifier on microphone or wav file input. """ def __init__(self, featurizer_model=None, classifier_model=None, auto_scale=True, sample_rate=None, channels=None, input_device=None, categories=None, image_width=80, threshold=None, wav_file=None, clear=5, serial=None, vad_model=None, smoothing=None, ignore_list=None): """ Initialize AudioDemo object featurizer_model - the path to the ELL featurizer classifier_model - the path to the ELL classifier auto_scale - auto scale audio input to range [-1, 1] sample_rate - sample rate to featurizer is expecting channels - number of channels featurizer is expecting input_device - optional id of microphone to use categories - path to file containing category labels image_width - width of the spectrogram image threshold - ignore predictions that have confidence below this number (e.g. 0.5) wav_file - optional wav_file to use when you click Play serial - optional serial input, reading numbers from the given serial port. vad_model - optional ELL model containing VoiceActivityDetector smoothing - controls the size of the smoothing window (defaults to 0). ignore_list - list of category labels to ignore (like 'background' or 'silence') """ super().__init__() self.CLASSIFIER_MODEL_KEY = "classifier_model" self.FEATURIZER_MODEL_KEY = "featurizer_model" self.WAV_FILE_KEY = "wav_file" self.CATEGORY_FILE_KEY = "categories" self.get_settings_file_name() self.load_settings() self.reading_input = False self.featurizer_model = None self.serial_port = serial self.smoothing = smoothing self.ignore_list = ignore_list if featurizer_model: self.featurizer_model = featurizer_model self.settings[self.FEATURIZER_MODEL_KEY] = featurizer_model elif self.FEATURIZER_MODEL_KEY in self.settings: self.featurizer_model = self.settings[self.FEATURIZER_MODEL_KEY] self.classifier_model = None if classifier_model: self.classifier_model = classifier_model self.settings[self.CLASSIFIER_MODEL_KEY] = classifier_model elif self.CLASSIFIER_MODEL_KEY in self.settings: self.classifier_model = self.settings[self.CLASSIFIER_MODEL_KEY] self.wav_filename = wav_file if self.wav_filename is None and self.WAV_FILE_KEY in self.settings: self.wav_filename = self.settings[self.WAV_FILE_KEY] self.wav_file_list = None self.auto_scale = auto_scale self.sample_rate = sample_rate if sample_rate is not None else 16000 self.channels = channels if channels is not None else 1 self.input_device = input_device self.num_classifier_features = None self.vad = None self.vad_reset = (vad_model is not None) self.previous_vad = 0 self.vad_latch = 3 # only reset after 3 vad=0 signals to smooth vad signal a bit. if not categories and self.CATEGORY_FILE_KEY in self.settings: categories = self.settings[self.CATEGORY_FILE_KEY] self.categories = categories if categories: self.settings[self.CATEGORY_FILE_KEY] = categories self.save_settings() # in case we just changed it. self.audio_level = 0 self.min_level = 0 self.max_level = 0 self.threshold = threshold self.output_clear_time = int(clear * 1000) if clear else 5000 self.featurizer = None self.classifier = None self.wav_file = None self.speaker = None self.microphone = None self.animation = None self.show_classifier_output = True self.last_prediction = None self.probability = 0 # Threads self.read_input_thread = None self.lock = Lock() self.main_thread = get_ident() self.message_queue = [] # UI components self.max_spectrogram_width = image_width self.features_entry = None self.classifier_feature_data = None self.spectrogram_image_data = None self.init_ui() if self.featurizer_model: self.load_featurizer_model(os.path.abspath(self.featurizer_model)) else: self.show_output("Please specify and load a feature model") if smoothing == "vad": # smooth up to 1 second worth of predictions self.smoothing = int(self.sample_rate / self.featurizer.input_size) if vad_model is None: vad_model = make_vad.make_vad("vad.ell", self.sample_rate, self.featurizer.input_size, self.featurizer.output_size, None) if self.classifier_model: self.load_classifier(self.classifier_model) self.setup_spectrogram_image() else: self.show_output("Please specify and load a classifier model") if vad_model: self.vad = vad.VoiceActivityDetector(vad_model) def get_settings_file_name(self): """ this app stores the various UI field values in a settings file in your temp folder so you don't always have to specify the full command line options """ import tempfile temp = tempfile.gettempdir() self.settings_file_name = os.path.join(temp, "ELL", "Audio", "viewaudio.json") def load_settings(self): """ load the previously saved settings from disk, if any """ self.settings = {} print("loading settings from: {}".format(self.settings_file_name)) if os.path.isfile(self.settings_file_name): with open(self.settings_file_name, "r") as f: self.settings = json.load(f) def save_settings(self): """ save the current settings to disk """ settings_dir = os.path.dirname(self.settings_file_name) if not os.path.isdir(settings_dir): os.makedirs(settings_dir) with open(self.settings_file_name, "w") as f: json.dump(self.settings, f, indent=2) def load_featurizer_model(self, featurizer_model): """ load the given compiled ELL featurizer for use in processing subsequent audio input """ if featurizer_model: self.featurizer = featurizer.AudioTransform(featurizer_model, 40) self.setup_spectrogram_image() self.show_output("Feature input size: {}, output size: {}".format( self.featurizer.input_size, self.featurizer.output_size)) if self.features_entry.get() != featurizer_model: self.features_entry.delete(0, END) self.features_entry.insert(0, featurizer_model) self.init_data() def load_classifier(self, classifier_path): """ load the given compiled ELL classifier for use in processing subsequent audio input """ if classifier_path: self.classifier = classifier.AudioClassifier( classifier_path, self.categories, self.threshold, smoothing_window=self.smoothing, ignore_list=self.ignore_list) self.show_output( "Classifier input size: {}, output size: {}".format( self.classifier.input_size, self.classifier.output_size)) if self.classifier_entry.get() != classifier_path: self.classifier_entry.delete(0, END) self.classifier_entry.insert(0, classifier_path) self.init_data() def init_data(self): """ initialize the spectrogram_image_data and classifier_feature_data based on the newly loaded model info """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) if self.spectrogram_widget: self.spectrogram_widget.clear(self.spectrogram_image_data) if self.classifier: self.num_classifier_features = self.classifier.input_size // self.featurizer.output_size dim = (self.num_classifier_features, self.featurizer.output_size) self.classifier_feature_data = np.zeros(dim, dtype=float) def accumulate_feature(self, feature_data): """ accumulate the feature data and pass feature data to classifier """ if self.vad: vad_signal = self.vad.predict(feature_data) if self.previous_vad != vad_signal: if vad_signal == 0: if self.vad_latch > 0: # wait for 2 more to smooth the vad signal a bit. self.vad_latch -= 1 else: self.vad_latch = 3 self.previous_vad = vad_signal if self.vad_reset: self.show_output("--- reset ---") self.classifier.reset() else: self.show_output("--- clear history ---") self.classifier.clear_smoothing() elif vad_signal == 1: self.vad_latch = 3 self.previous_vad = vad_signal self.audio_level = np.sum([x * x for x in feature_data]) if self.classifier and self.show_classifier_output: self.classifier_feature_data = np.vstack( (self.classifier_feature_data, feature_data))[-self.num_classifier_features:, :] self.evaluate_classifier() def accumulate_spectrogram_image(self, feature_data): """ accumulate the feature data into the spectrogram image """ image_data = self.spectrogram_image_data feature_data = np.reshape(feature_data, [-1, 1]) new_image = np.hstack( (image_data, feature_data))[:, -image_data.shape[1]:] image_data[:, :] = new_image def update_rgb_led(self): # This helper function uses the RGB led UI to give an indication of audio levels (brightness) # and voice activity (red) level = self.audio_level if level < self.min_level: self.min_level = level if level > self.max_level: self.max_level = level red = 0.0 green = 0.0 blue = 0.0 range = self.max_level - self.min_level if range == 0: range = 1.0 brightness = 128 * (level - self.min_level) / range if self.previous_vad: red = brightness + 127 else: green = brightness + 127 rgb = "#{:02x}{:02x}{:02x}".format(int(red), int(green), int(blue)) self.rgb_canvas.itemconfig(self.rgb_oval, fill=rgb) def on_ui_update(self): # this is an animation callback to update the UI every 33 milliseconds. self.update_rgb_led() self.process_output() result = self.set_spectrogram_image() if not self.reading_input: self.after(1, self.on_stopped) return (result, ) def set_spectrogram_image(self): """ update the spectrogram image and the min/max values """ self.lock.acquire() # protect access to the shared state result = self.spectrogram_widget.show(self.spectrogram_image_data) self.lock.release() return result def get_correct_shape(self, shape): """ for some reason keras stores input shape as (None,80,40), and numpy hates that so we have to change this to (1,80,40) """ shape = list(shape) fix = [x if x else 1 for x in shape] return tuple(fix) def clear_output(self): """ remove some of the Output based a the timeout callback """ self.output_text.delete(1.0, 2.0) def process_output(self): """ show output that was queued by background thread """ self.lock.acquire() messages = self.message_queue self.message_queue = [] self.lock.release() for msg in messages: self.show_output(msg) def show_output(self, message): """ show output message, or queue it if we are on a background thread """ if self.main_thread != get_ident(): self.message_queue += [message] return for line in str(message).split('\n'): self.output_text.insert(END, "{}\n".format(line)) self.output_text.see("end") # scroll to end self.after(self.output_clear_time, self.clear_output) def evaluate_classifier(self): """ run the classifier model on the current feature data and show the prediction, if any """ if self.evaluate_classifier and self.classifier and self.classifier_feature_data is not None: prediction, probability, label, _ = self.classifier.predict( self.classifier_feature_data.ravel()) if prediction is not None: percent = int(100 * probability) if self.last_prediction != prediction or self.probability < probability: self.last_prediction = prediction self.probability = probability self.show_output(" DETECTED ({}) {}% {}".format( prediction, percent, label)) def start_playing(self, filename): """ Play a wav file, and classify the audio. Note we use a background thread to read the wav file and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the audio playback """ if self.speaker is None: self.speaker = speaker.Speaker() self.stop() self.reading_input = False self.wav_file = wav_reader.WavReader(self.sample_rate, self.channels, self.auto_scale) self.wav_file.open(filename, self.featurizer.input_size, self.speaker) def update_func(frame_index): return self.on_ui_update() if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) self.animation = self.spectrogram_widget.begin_animation(update_func) # start background thread to read and classify the audio. self.featurizer.open(self.wav_file) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def start_recording(self): """ Start recording audio from the microphone nd classify the audio. Note we use a background thread to process the audio and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the microphone readings """ self.stop() input_channel = None if self.serial_port: import serial_reader self.serial = serial_reader.SerialReader(0.001) self.serial.open(self.featurizer.input_size, self.serial_port) input_channel = self.serial else: if self.microphone is None: self.microphone = microphone.Microphone( auto_scale=self.auto_scale, console=False) num_channels = 1 self.microphone.open(self.featurizer.input_size, self.sample_rate, num_channels, self.input_device) input_channel = self.microphone def update_func(frame_index): return self.on_ui_update() if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) self.animation = self.spectrogram_widget.begin_animation(update_func) # start background thread to read and classify the recorded audio. self.featurizer.open(input_channel) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def on_read_features(self): """ this is the background thread entry point. So we read the feature data in a loop and pass it to the classifier """ try: while self.reading_input and self.featurizer: feature_data = self.featurizer.read() if feature_data is None: break # eof else: self.lock.acquire() self.accumulate_feature(feature_data) self.accumulate_spectrogram_image(feature_data) self.lock.release() except: errorType, value, traceback = sys.exc_info() print("### Exception reading input: " + str(errorType) + ": " + str(value) + " " + str(traceback)) while traceback: print(traceback.tb_frame.f_code) traceback = traceback.tb_next self.reading_input = False if self.classifier: self.classifier.reset() # good time to reset. def stop(self): """ called when user clicks the stop button, or we reach the end of a wav file input """ # close streams if self.animation: self.animation.event_source.stop() self.animation = None if self.microphone: self.microphone.close() if self.speaker: self.speaker.close() if self.wav_file: self.wav_file.close() self.wav_file = None self.reading_input = False self.last_prediction = None self.probability = 0 if self.classifier: self.classifier.reset() # good time to reset. def on_rec_button_click(self): """ called when user clicks the record button, same button is used to "stop" recording. """ if self.rec_button["text"] == "Rec": self.rec_button["text"] = "Stop" self.play_button["text"] = "Play" self.start_recording() else: self.rec_button["text"] = "Rec" self.on_stop() def on_play_button_click(self): """ called when user clicks the record button, same button is used to "stop" playback """ if self.play_button["text"] == "Play": self.play_button["text"] = "Stop" self.rec_button["text"] = "Rec" self.on_play() else: self.play_button["text"] = "Play" self.on_stop() def on_play(self): """ called when user clicks the Play button """ filename = self.wav_filename_entry.get() filename = filename.strip('"') self.wav_filename = filename self.settings[self.WAV_FILE_KEY] = filename self.save_settings() self.start_playing(filename) def on_stop(self): """ called when user clicks the Stop button """ self.reading_input = False if self.wav_file: self.wav_file.close() self.wav_file = None if self.read_input_thread: self.read_input_thread.join() self.read_input_thread = None self.stop() def on_stopped(self): """ called when we reach the end of the wav file playback """ self.play_button["text"] = "Play" self.stop() def get_wav_list(self): if self.wav_filename and os.path.isfile(self.wav_filename): dir_name = os.path.dirname(self.wav_filename) if not self.wav_file_list: self.wav_file_list = [ x for x in os.listdir(dir_name) if os.path.splitext(x)[1] == ".wav" ] self.wav_file_list.sort() return self.wav_file_list def select_wav_file(self, filename): self.wav_filename = filename # show the file in the UI self.wav_filename_entry.delete(0, END) if self.wav_filename: self.wav_filename_entry.insert(0, self.wav_filename) # and automatically play the file. self.on_play() def on_minus_key(self, event): """ When user presses the plus button we reverse to the previous wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i - 1 >= 0: next_wav_file = self.wav_file_list[i - 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def on_plus_key(self, event): """ When user presses the plus button we advance to the next wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i + 1 < len(self.wav_file_list): next_wav_file = self.wav_file_list[i + 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def init_ui(self): """ setup the GUI for the app """ self.master.title("Test") self.pack(fill=BOTH, expand=True) # Input section input_frame = LabelFrame(self, text="Input") input_frame.bind("-", self.on_minus_key) input_frame.bind("+", self.on_plus_key) input_frame.pack(fill=X) self.play_button = Button(input_frame, text="Play", command=self.on_play_button_click) self.play_button.pack(side=RIGHT, padx=4) self.rgb_canvas = tk.Canvas(input_frame, width=20, height=20, bd=0) self.rgb_oval = self.rgb_canvas.create_oval(2, 2, 20, 20, fill='#FF0000', width=0) self.rgb_canvas.pack(side=RIGHT, padx=4) self.rec_button = Button(input_frame, text="Rec", command=self.on_rec_button_click) self.rec_button.pack(side=RIGHT, padx=4) self.wav_filename_entry = Entry(input_frame, width=24) self.wav_filename_entry.pack(fill=X) self.wav_filename_entry.delete(0, END) if self.wav_filename: self.wav_filename_entry.insert(0, self.wav_filename) # Feature section features_frame = LabelFrame(self, text="Features") features_frame.pack(fill=X) features_control_frame = Frame(features_frame) features_control_frame.pack(fill=X) load_features_button = Button(features_control_frame, text="Load", command=self.on_load_featurizer_model) load_features_button.pack(side=RIGHT) self.features_entry = Entry(features_control_frame, width=8) self.features_entry.pack(fill=X) self.features_entry.delete(0, END) if self.featurizer_model: self.features_entry.insert(0, self.featurizer_model) self.spectrogram_widget = SpectrogramImage(features_frame) self.spectrogram_widget.pack(fill=X) # Classifier section classifier_frame = LabelFrame(self, text="Classifier") classifier_frame.pack(fill=X) load_classifier_button = Button(classifier_frame, text="Load", command=self.on_load_classifier) load_classifier_button.pack(side=RIGHT) self.classifier_entry = Entry(classifier_frame, width=8) self.classifier_entry.pack(fill=X) self.classifier_entry.delete(0, END) if self.classifier_model: self.classifier_entry.insert(0, self.classifier_model) # Output section output_frame = LabelFrame(self, text="Output") output_frame.pack(fill=BOTH, expand=True) self.output_text = Text(output_frame) self.output_text.pack(fill=BOTH, padx=4, expand=True) def setup_spectrogram_image(self): """ this needs to be called if you load a new feature model, because the featurizer output size might have changed. """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) self.spectrogram_widget.show(self.spectrogram_image_data) def on_load_featurizer_model(self): """ called when user clicks the Load button for the feature model """ filename = self.features_entry.get() filename = filename.strip('"') self.settings[self.FEATURIZER_MODEL_KEY] = filename self.save_settings() self.stop() self.load_featurizer_model(filename) def on_load_classifier(self): """ called when user clicks the Load button for the feature model """ self.classifier_model = self.classifier_entry.get() self.settings[self.CLASSIFIER_MODEL_KEY] = self.classifier_model self.save_settings() self.stop() self.load_classifier(self.classifier_model)
class ManagerWindow: """ 主管理界面入口类,直接无参数创建对象即可。 """ # 窗口宽高 WIN_WIDTH = 800 WIN_HEIGHT = 600 def __init__(self): # 界面根节点 self.root = Tk() # 主窗口标题 self.root.title(MANAGER_TITLE) # 读取config self.config_dict = ConfigOperation.get_dir_from_file() # 主窗口分辨率 self.root.geometry("%sx%s+%s+%s" % ( self.WIN_WIDTH, self.WIN_HEIGHT, int((self.root.winfo_screenwidth() - self.WIN_WIDTH) / 2), int((self.root.winfo_screenheight() - self.WIN_HEIGHT) / 2) )) self.root.minsize(self.WIN_WIDTH, self.WIN_HEIGHT) # 选项卡 self.tab_main = Notebook(self.root) self.tab_main.pack(expand=True, fill=BOTH) # 登录选项卡 self.frame_login = Frame(self.tab_main, bg=BG_COLOR) self.frame_login.pack(side=TOP) self.tab_main.add(self.frame_login, text=TAB_NAME_LIST["login"]["text"]) # 管理选项卡 self.frame_manage = Frame(self.tab_main, bg=BG_COLOR) self.tab_main.add(self.frame_manage, text=TAB_NAME_LIST["manage"]["text"]) # 好友选项卡 self.frame_friend = Frame(self.tab_main, bg=BG_COLOR) self.frame_friend.pack(side=TOP) self.tab_main.add(self.frame_friend, text=TAB_NAME_LIST["friends"]["text"]) # 群选项卡 self.frame_group = Frame(self.tab_main, bg=BG_COLOR) self.frame_group.pack(side=TOP) self.tab_main.add(self.frame_group, text=TAB_NAME_LIST["groups"]["text"]) # 插件选项卡 self.frame_plugin = Frame(self.tab_main, bg=BG_COLOR) self.frame_plugin.pack(side=TOP) self.tab_main.add(self.frame_plugin, text=TAB_NAME_LIST["plugins"]["text"]) # 初始化登录选项卡 self.__init_login_tab() # 初始化好友选项卡 self.__init_friend_tab() # 初始化群选项卡 self.__init_group_tab() # 初始化管理选项卡 self.__init_manage_tab() # 初始化插件选项卡 self.__init_plugin_tab() # 关闭窗口自动释放Session self.root.protocol("WM_DELETE_WINDOW", lambda: self.__on_close_root()) # 刷新显示 self.__refresh() # 运行相关线程 fetch_message_thread = FetchMessageThread() fetch_message_thread.daemon = True fetch_message_thread.start() # 运行插件初始化方法 PluginHandler.call_init() # 执行自动连接一次 self.__auto_connect() # 显示 self.root.mainloop() def __init_login_tab(self): """ 初始化登录选项卡界面 :return: 无 """ # 左边列表的frame frame_login_list = Frame(self.frame_login, bg=BG_COLOR) frame_login_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 列表,用于保存连接记录 self.treeview_login_list = Treeview( frame_login_list, columns=[ LOGIN_GUIDE["host"], LOGIN_GUIDE["port"], LOGIN_GUIDE["authkey"], LOGIN_GUIDE["qq"] ], show="headings", selectmode=BROWSE ) self.treeview_login_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_login_list.column( LOGIN_GUIDE["host"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["host"], text=LOGIN_GUIDE["host"] ) self.treeview_login_list.column( LOGIN_GUIDE["port"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["port"], text=LOGIN_GUIDE["port"] ) self.treeview_login_list.column( LOGIN_GUIDE["authkey"], width=40 ) self.treeview_login_list.heading( LOGIN_GUIDE["authkey"], text=LOGIN_GUIDE["authkey"] ) self.treeview_login_list.column( LOGIN_GUIDE["qq"], width=0 ) self.treeview_login_list.heading( LOGIN_GUIDE["qq"], text=LOGIN_GUIDE["qq"] ) # 设定双击事件 self.treeview_login_list.bind( "<Double-Button-1>", lambda event: self.__on_double_click_login_list_content() ) # 设定登录列表的滚动条 scrollbar_login_list = Scrollbar(frame_login_list) scrollbar_login_list.pack(fill="y", expand=True) self.treeview_login_list.config(yscrollcommand=scrollbar_login_list.set) scrollbar_login_list.config(command=self.treeview_login_list.yview) # 设置列表右键菜单 self.treeview_login_list.bind("<Button-3>", self.__show_login_list_pop_up_menu) # 登录界面显示的那一坨 frame_login_menu = Frame(self.frame_login, bg=BG_COLOR) frame_login_menu.pack(side=LEFT, padx=5, pady=5) # mirai端地址 Label(frame_login_menu, text=LOGIN_GUIDE["host"], bg=BG_COLOR).grid(row=0, sticky=E, padx=5, pady=5) self.entry_host = Entry(frame_login_menu) self.entry_host.grid(row=0, column=1, sticky=W, padx=5, pady=5) # mirai端端口号 Label(frame_login_menu, text=LOGIN_GUIDE["port"], bg=BG_COLOR).grid(row=1, sticky=E, padx=5, pady=5) self.entry_port = Entry(frame_login_menu) self.entry_port.grid(row=1, column=1, sticky=W, padx=5, pady=5) # mirai端http授权码 Label(frame_login_menu, text=LOGIN_GUIDE["authkey"], bg=BG_COLOR).grid( row=2, sticky=E, padx=5, pady=5 ) self.entry_authkey = Entry(frame_login_menu, show=PWD_CHAR_CIRCLE) self.entry_authkey.grid(row=2, column=1, sticky=W, padx=5, pady=5) # 用于激活sessioKey的qq号码 Label(frame_login_menu, text=LOGIN_GUIDE["qq"], bg=BG_COLOR).grid( row=3, sticky=E, padx=5, pady=5 ) self.entry_qq = Entry(frame_login_menu) self.entry_qq.grid(row=3, column=1, sticky=W, padx=5, pady=5) # 自动连接复选框 self.auto_connect_var = BooleanVar() self.checkbutton_auto_connect = Checkbutton( frame_login_menu, text=AUTO_CONNECT_GUIDE, onvalue=True, offvalue=False, variable=self.auto_connect_var, bg=BG_COLOR ) self.checkbutton_auto_connect.grid(row=4, column=0, padx=5, pady=5, columnspan=2) # 连接按钮 self.btn_connect = Button( frame_login_menu, text=BTN_TEXT_CONN["connect"], width=15, command=lambda: self.__on_click_connect_event(), ) self.btn_connect.grid(row=5, columnspan=2, padx=5, pady=5) # 添加到登录列表按钮 self.btn_save_login = Button( frame_login_menu, width=15, text=BTN_TEXT_ADD_LOGIN, command=lambda: self.__on_click_add_to_login_list() ) self.btn_save_login.grid(row=6, columnspan=2, padx=5, pady=5) # 状态栏 self.label_login_status_bar = Label( self.root, text=LOGIN_STATUS_BAR_TEXT["notConnect"], fg=STATUS_BAR_COLOR["normal"] ) self.label_login_status_bar.pack(side=LEFT) # 下面开始从config中将内容填充进文本框中 self.entry_host.delete(0, END) self.entry_host.insert(END, self.config_dict["lastConnection"]["host"]) self.entry_port.delete(0, END) self.entry_port.insert(END, self.config_dict["lastConnection"]["port"]) self.entry_authkey.delete(0, END) self.entry_authkey.insert(END, self.config_dict["lastConnection"]["authkey"]) self.entry_qq.delete(0, END) self.entry_qq.insert(END, self.config_dict["lastConnection"]["qq"]) # 自动连接复选框内容 self.auto_connect_var.set(self.config_dict["autoConnect"]) def __init_friend_tab(self): """ 初始化好友选项卡内容 :return: 无 """ # 创建好友列表框架 frame_friend_list = Frame(self.frame_friend, bg=BG_COLOR) frame_friend_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 创建消息测试发送框架 frame_friend_send = Frame(self.frame_friend, bg=BG_COLOR) frame_friend_send.pack( side=LEFT, padx=5, pady=5 ) # 设置列表 self.treeview_friend_list = Treeview( frame_friend_list, columns=[ FRIEND_GUIDE["qq"], FRIEND_GUIDE["nickname"], FRIEND_GUIDE["remark"] ], show="headings", selectmode=BROWSE ) self.treeview_friend_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_friend_list.column( FRIEND_GUIDE["qq"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["qq"], text=FRIEND_GUIDE["qq"] ) self.treeview_friend_list.column( FRIEND_GUIDE["nickname"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["nickname"], text=FRIEND_GUIDE["nickname"] ) self.treeview_friend_list.column( FRIEND_GUIDE["remark"], width=0 ) self.treeview_friend_list.heading( FRIEND_GUIDE["remark"], text=FRIEND_GUIDE["remark"] ) # 设定好友列表的滚动条 scrollbar_friend_list = Scrollbar(frame_friend_list) scrollbar_friend_list.pack(fill="y", expand=True) self.treeview_friend_list.config(yscrollcommand=scrollbar_friend_list.set) scrollbar_friend_list.config(command=self.treeview_friend_list.yview) # 刷新列表按钮 Button( frame_friend_send, text=BTN_FRIEND_REFRESH, command=lambda: self.__on_click_refresh_friend_list_event() ).grid(row=0, padx=5, pady=5) # 发送纯文本窗口标题 Label(frame_friend_send, text=SEND_TITLE, bg=BG_COLOR).grid(row=1, padx=5, pady=5) # 发送纯文本窗口 self.text_friend_send = Text(frame_friend_send, width=30, height=5) self.text_friend_send.grid(row=2, padx=5, pady=5) # 发送按钮 Button( frame_friend_send, text=BTN_SEND, command=lambda: self.__on_click_send_friend_message() ).grid(row=3, padx=5, pady=5) def __init_group_tab(self): """ 初始化群选项卡内容 :return: 无 """ # 创建好友列表框架 frame_group_list = Frame(self.frame_group, bg=BG_COLOR) frame_group_list.pack( side=LEFT, expand=True, fill=BOTH, padx=5, pady=5 ) # 创建消息测试发送框架 frame_group_send = Frame(self.frame_group, bg=BG_COLOR) frame_group_send.pack( side=LEFT, padx=5, pady=5 ) # 设置列表 self.treeview_group_list = Treeview( frame_group_list, columns=[ GROUP_GUIDE["group"], GROUP_GUIDE["name"], GROUP_GUIDE["permission"] ], show="headings", selectmode=BROWSE ) self.treeview_group_list.pack( expand=True, fill=BOTH, side=LEFT ) self.treeview_group_list.column( GROUP_GUIDE["group"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["group"], text=GROUP_GUIDE["group"] ) self.treeview_group_list.column( GROUP_GUIDE["name"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["name"], text=GROUP_GUIDE["name"] ) self.treeview_group_list.column( GROUP_GUIDE["permission"], width=0 ) self.treeview_group_list.heading( GROUP_GUIDE["permission"], text=GROUP_GUIDE["permission"] ) # 设定群列表的滚动条 scrollbar_group_list = Scrollbar(frame_group_list) scrollbar_group_list.pack(fill="y", expand=True) self.treeview_group_list.config(yscrollcommand=scrollbar_group_list.set) scrollbar_group_list.config(command=self.treeview_group_list.yview) # 刷新列表按钮 Button( frame_group_send, text=BTN_GROUP_REFRESH, command=lambda: self.__on_click_refresh_group_list_event() ).grid(row=0, padx=5, pady=5) # 发送纯文本窗口标题 Label(frame_group_send, text=SEND_TITLE, bg=BG_COLOR).grid(row=1, padx=5, pady=5) # 发送纯文本窗口 self.text_group_send = Text(frame_group_send, width=30, height=5) self.text_group_send.grid(row=2, padx=5, pady=5) # 发送按钮 Button( frame_group_send, text=BTN_SEND, command=lambda: self.__on_click_send_group_message() ).grid(row=3, padx=5, pady=5) def __init_manage_tab(self): """ 初始化管理选项卡 :return: 无 """ f_manage = Frame(self.frame_manage, bg=BG_COLOR) f_manage.pack(padx=5, pady=5, expand=True) # 指定头指示 Label(f_manage, text=MANAGE_GUIDE["commandHead"], bg=BG_COLOR).grid( row=0, column=0, padx=5, pady=5, sticky=E ) # 指令头文本框 self.entry_command_head = Entry(f_manage) self.entry_command_head.grid(row=0, column=1, padx=5, pady=5, sticky=EW) # 调试复选框 self.debug_var = BooleanVar() checkbutton_debug = Checkbutton( f_manage, text=MANAGE_GUIDE["debug"], onvalue=True, offvalue=False, variable=self.debug_var, bg=BG_COLOR ) checkbutton_debug.grid(row=1, column=0, columnspan=3, padx=5, pady=5) # 启用机器人 self.enable_var = BooleanVar() checkbutton_enable = Checkbutton( f_manage, text=MANAGE_GUIDE["enable"], onvalue=True, offvalue=False, variable=self.enable_var, bg=BG_COLOR ) checkbutton_enable.grid(row=2, column=0, columnspan=3, padx=5, pady=5) # 配置保存 Button( f_manage, text=MANAGE_GUIDE["saveConfig"], command=self.__on_click_save_config ).grid( row=3, column=1, padx=5, pady=5, sticky=EW ) # bot管理qq列表 self.treeview_op_list = Treeview( f_manage, columns=[ MANAGE_GUIDE["botOpQQ"] ], show="headings", selectmode=BROWSE ) self.treeview_op_list.column(MANAGE_GUIDE["botOpQQ"], width=200) self.treeview_op_list.heading(MANAGE_GUIDE["botOpQQ"], text=MANAGE_GUIDE["botOpQQ"]) self.treeview_op_list.grid( row=4, column=0, columnspan=3, rowspan=10, sticky=EW ) # 列表右键 self.treeview_op_list.bind("<Button-3>", self.__show_op_list_pop_up_menu) # 添加管理标签 Label(f_manage, text=MANAGE_GUIDE["addOpQQ"], bg=BG_COLOR).grid(row=14, column=0, padx=5, pady=5) # 添加管理文本框 self.entry_add_op = Entry(f_manage) self.entry_add_op.grid(row=14, column=1, padx=5, pady=5) # 添加添加按钮 Button( f_manage, text=MANAGE_GUIDE["btnAddOpQQ"], command=lambda: self.__on_click_add_op() ).grid(row=14, column=2, padx=5, pady=5, sticky=EW) def __init_plugin_tab(self): """ 初始化插件选项卡 :return: 无 """ # 指示标签 Label(self.frame_plugin, text=PLUGIN_LABEL_TEXT, bg=BG_COLOR).pack(side=TOP) # 插件列表frame frame_plugin_list = Frame(self.frame_plugin, bg=BG_COLOR) frame_plugin_list.pack( side=TOP, expand=True, fill=BOTH, padx=5, pady=5 ) # 插件列表 self.treeview_plugin_list = Treeview( frame_plugin_list, columns=[ PLUGIN_GUIDE["pluginName"] ], show="headings", selectmode=BROWSE ) self.treeview_plugin_list.pack(fill=BOTH, expand=True, side=LEFT) self.treeview_plugin_list.column(PLUGIN_GUIDE["pluginName"]) self.treeview_plugin_list.heading(PLUGIN_GUIDE["pluginName"], text=PLUGIN_GUIDE["pluginName"]) # 设定插件列表滚动条 scrollbar_plugin_list = Scrollbar(frame_plugin_list) scrollbar_plugin_list.pack(fill="y", expand=True) self.treeview_plugin_list.config(yscrollcommand=scrollbar_plugin_list.set) scrollbar_plugin_list.config(command=self.treeview_plugin_list.yview) def __on_click_connect_event(self): """ 点击连接按钮事件 :return: 无 """ if not GlobalValues.is_connected: # 如果是要连接 # 存到全局使用变量 GlobalValues.conn_host = self.entry_host.get() GlobalValues.conn_port = self.entry_port.get() GlobalValues.conn_authkey = self.entry_authkey.get() try: # 转换为整型后保存 GlobalValues.conn_qq = int(self.entry_qq.get()) except ValueError: self.label_login_status_bar.config(text=LOGIN_STATUS_BAR_TEXT["wrongQQ"], fg=STATUS_BAR_COLOR["failed"]) return # 修改界面上的一些内容为不可修改 self.__set_login_tools_active(False) # 修改按钮内容 self.btn_connect.config(text=BTN_TEXT_CONN["disconnect"]) # 修改状态栏内容 self.label_login_status_bar.config(text=LOGIN_STATUS_BAR_TEXT["connecting"], fg=STATUS_BAR_COLOR["normal"]) # 调用连接 try: Conn.new_session_key() except ( requests.exceptions.InvalidURL, requests.exceptions.ConnectionError, ): # 连接错误 # 错误信息显示到状态栏 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["connectFailed"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return except WrongAuthkeyException: # 授权码错误 # 显示到状态栏 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["wrongAuthkey"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return except BotNotExistException: # bot不存在错误 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["qqNotExist"], fg=STATUS_BAR_COLOR["failed"] ) # 修改文本框为可修改 self.__set_login_tools_active(True) self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) return self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["connected"], fg=STATUS_BAR_COLOR["passed"] ) # 修改连接状态值 GlobalValues.is_connected = True # 修改上次连接键值对 ConfigOperation.modify_dict("lastConnection", { "host": GlobalValues.conn_host, "port": GlobalValues.conn_port, "authkey": GlobalValues.conn_authkey, "qq": GlobalValues.conn_qq }) # 修改文件中自动连接开关 ConfigOperation.modify_dict("autoConnect", self.auto_connect_var.get()) else: # 如果要断开连接 # 修改文本框为可修改 self.__set_login_tools_active(True) # 修改按钮名称 self.btn_connect.config(text=BTN_TEXT_CONN["connect"]) # 修改属性值 self.label_login_status_bar.config( text=LOGIN_STATUS_BAR_TEXT["disconnectSuccess"], fg=STATUS_BAR_COLOR["normal"] ) # 释放session Conn.release_session_key() # 修改连接状态值 GlobalValues.is_connected = False def __set_login_tools_active(self, active: bool): """ 修改界面上的一些内容为不可修改 :param active: bool,如果为False则禁用掉文本框,否则启用 :return: 无 """ if active: self.entry_host.config(state=ACTIVE) self.entry_port.config(state=ACTIVE) self.entry_authkey.config(state=ACTIVE) self.entry_qq.config(state=ACTIVE) self.checkbutton_auto_connect.config(state=ACTIVE) else: self.entry_host.config(state=DISABLED) self.entry_port.config(state=DISABLED) self.entry_authkey.config(state=DISABLED) self.entry_qq.config(state=DISABLED) self.checkbutton_auto_connect.config(state=DISABLED) def __on_close_root(self): """ 关闭窗口的事件 :return: 无 """ # 如果正在连接则释放连接 if GlobalValues.is_connected: Conn.release_session_key() # 杀掉root self.root.destroy() def __refresh(self): """ 用于刷新界面,在必要时调用 :return: 无 """ def refresh_login_list(): """ 刷新登录列表 :return: 无 """ # 删除目前表中的所有内容 self.treeview_login_list.delete(*self.treeview_login_list.get_children()) # 把登录列表内容添加到显示中 for one_record in LoginListOperation.get_list_from_file(): self.treeview_login_list.insert("", index=END, values=( one_record["host"], one_record["port"], one_record["authkey"], one_record["qq"] )) def refresh_op_list(): """ 刷新bot管理员qq列表 :return: 无 """ # 删除目前表中的所有内容 self.treeview_op_list.delete(*self.treeview_op_list.get_children()) # 把内容添加到显示中 for one_record in OpListOperation.get_list(): self.treeview_op_list.insert("", index=END, values=( one_record )) def refresh_config(): """ 刷新配置 :return: 无 """ # 重新获取config self.config_dict = ConfigOperation.get_dir_from_file() # 将文件中的内容显示到界面中 self.entry_command_head.delete(0, END) self.entry_command_head.insert(END, self.config_dict["commandHead"]) # 设置复选框默认勾选 self.debug_var.set(self.config_dict["debug"]) self.enable_var.set(self.config_dict["enable"]) # 将内容设置到全局变量 GlobalValues.command_head = self.config_dict["commandHead"] GlobalValues.debug_var = self.debug_var GlobalValues.enable_var = self.enable_var def refresh_plugin_list(): # 获取插件名称 plugin_names = PluginHandler.get_plugin_name_list() # 显示 self.treeview_plugin_list.delete(*self.treeview_plugin_list.get_children()) for name in plugin_names: self.treeview_plugin_list.insert("", index=END, values=( name )) # 调用刷新登录列表 refresh_login_list() # 调用刷新op列表 refresh_op_list() # 刷新config显示 refresh_config() # 刷新插件列表显示 refresh_plugin_list() def __on_click_add_to_login_list(self): """ 将填写内容添加到列表中 :return: 无 """ # 非空检测 if [ self.entry_host.get(), self.entry_port.get(), self.entry_authkey.get(), self.entry_qq.get() ] == [""] * 4: return # 调用添加登录项方法 LoginListOperation.add_to_list( self.entry_host.get(), self.entry_port.get(), self.entry_authkey.get(), self.entry_qq.get() ) # 刷新显示 self.__refresh() def __on_double_click_login_list_content(self): """ 双击登录列表项目时,自动填充到右侧 :return: 无 """ # 获取item的值 item_list = self.treeview_login_list.item(self.treeview_login_list.focus(), "values") # 获取需要的项目并设置 self.entry_host.delete(0, END) self.entry_host.insert(END, item_list[0]) self.entry_port.delete(0, END) self.entry_port.insert(END, item_list[1]) self.entry_authkey.delete(0, END) self.entry_authkey.insert(END, item_list[2]) self.entry_qq.delete(0, END) self.entry_qq.insert(END, item_list[3]) def __show_login_list_pop_up_menu(self, event): """ 显示右键菜单 :param event: 事件 :return: 无 """ def on_delete_event(item_id): """ 删除选项的事件 :return: 无 """ # 删除该项 LoginListOperation.remove_from_list(*self.treeview_login_list.item(item_id, "values")) self.treeview_login_list.delete(item_id) self.__refresh() # 获取选择对象 iid = self.treeview_login_list.identify_row(event.y) # 如果有选择,则弹出右键菜单 if iid: self.treeview_login_list.selection_set(iid) menu_pop_up = Menu(self.treeview_login_list, tearoff=False) menu_pop_up.add_command( label=POP_UP_MENU_DELETE_STR, command=lambda: on_delete_event(iid) ) menu_pop_up.post(event.x_root, event.y_root) def __on_click_refresh_friend_list_event(self): """ 点击刷新好友列表事件 :return: 无 """ try: # 如果未连接,则可能会抛出异常,此处直接弹出错误消息框 friend_list = Conn.get_friend_list() except: messagebox.showerror(message=REFRESH_ERROR_MSG) return # 删除列表内容 self.treeview_friend_list.delete(*self.treeview_friend_list.get_children()) # 解析friend_list for friend_block in friend_list: self.treeview_friend_list.insert("", index=END, values=( friend_block["id"], friend_block["nickname"], friend_block["remark"] )) def __on_click_refresh_group_list_event(self): """ 点击刷新群列表事件 :return: 无 """ try: # 如果未连接,则可能会抛出异常,此处直接弹出错误消息框 group_list = Conn.get_group_list() except: messagebox.showerror(message=REFRESH_ERROR_MSG) return # 删除列表内容 self.treeview_group_list.delete(*self.treeview_group_list.get_children()) # 解析group_list for group_block in group_list: self.treeview_group_list.insert("", index=END, values=( group_block["id"], group_block["name"], group_block["permission"] )) def __on_click_send_friend_message(self): """ 点击发送消息给好友按钮 :return: 无 """ # 获取到选中好友的值列表 value_list = self.treeview_friend_list.item(self.treeview_friend_list.focus(), "values") try: # 获取qq并发送消息 qq = value_list[0] message_chain = MessageChain() text = self.text_friend_send.get(1.0, END) if text == "\n": return message_chain.add_plain_text(text) Conn.send_friend_message(qq, message_chain) self.text_friend_send.delete(1.0, END) except: messagebox.showerror(message=SEND_ERROR_MSG) return def __on_click_send_group_message(self): """ 点击发送消息给群按钮 :return: 无 """ # 获取到选中群的值列表 value_list = self.treeview_group_list.item(self.treeview_group_list.focus(), "values") try: # 获取qq并发送消息 qq = value_list[0] message_chain = MessageChain() text = self.text_group_send.get(1.0, END) if text == "\n": return message_chain.add_plain_text(text) Conn.send_group_message(qq, message_chain) self.text_group_send.delete(1.0, END) except: messagebox.showerror(message=SEND_ERROR_MSG) return def __on_click_add_op(self): """ 点击添加op按钮事件 :return: 无 """ content = self.entry_add_op.get() # 如果添加op的文本框中没有东西,则不添加 if content == "": return # 如果转换数字出错则不添加 try: op_qq = int(content) except ValueError: return # 添加到op列表中 OpListOperation.add_to_list(op_qq) # 刷新显示 self.__refresh() def __show_op_list_pop_up_menu(self, event): """ op列表右键菜单 :return: 无 """ def on_delete_event(): """ 删除选项的事件 :return: 无 """ # 删除该项 # 注意此处的强转,由于能够保证显示出来的内容一定只含有数字,故可以直接转换 OpListOperation.remove_from_list(int(self.treeview_op_list.item(op_qq, "values")[0])) self.treeview_op_list.delete(op_qq) self.__refresh() # 获取选择对象 op_qq = self.treeview_op_list.identify_row(event.y) # 如果有选择,则弹出右键菜单 if op_qq: menu_pop_up = Menu(self.treeview_op_list, tearoff=False) self.treeview_op_list.selection_set(op_qq) menu_pop_up.add_command( label=POP_UP_MENU_DELETE_STR, command=lambda: on_delete_event() ) menu_pop_up.post(event.x_root, event.y_root) def __on_click_save_config(self): """ 点击保存配置事件 :return: 无 """ content = self.entry_command_head.get() # 如果为空,则不存入,但是刷新 # 这样是为了保证点击后会显示原来的设置 if content == "": self.__refresh() return ConfigOperation.modify_dict("commandHead", content) ConfigOperation.modify_dict("debug", self.debug_var.get()) ConfigOperation.modify_dict("enable", self.enable_var.get()) # 刷新 self.__refresh() # 弹出对话框 messagebox.showinfo(message=MANAGE_GUIDE["successSaveCommandHeadMsg"]) def __auto_connect(self): if self.config_dict["autoConnect"]: self.__on_click_connect_event()
class PageOne(tk.Frame): def __init__(self, master): tk.Frame.__init__(self, master) # tk.Frame.configure(self,bg='blue') # tk.Label(self, text="Page de jeu", font=('Helvetica', 18, "bold")).pack(side="top", fill=BOTH, pady=5) frame_left = Frame(self) self.frame_left = frame_left frame_left.pack(fill=BOTH, side=LEFT) # add entry to this frame self.label = tk.Label(frame_left, text="", font=('Helvetica', 10), fg='red') self.label.pack() self.bagniere_bleu = tk.Canvas(frame_left, width=50, height=3) self.bagniere_bleu.pack(side='top', anchor='c') self.bagniere_bleu.create_rectangle(0, 3, 50, 0, fill='blue') self.Nombre_1 = Entry(frame_left) self.Nombre_1.pack(side='top', anchor='w') # bagnier pour differencier les couleurs self.bagniere_bleu = tk.Canvas(frame_left, width=50, height=3) self.bagniere_bleu.pack(side='top', anchor='c') self.bagniere_bleu.create_rectangle(0, 3, 50, 0, fill='red') self.Nombre_2 = Entry(frame_left) self.Nombre_2.pack(side='top', anchor='w') tk.Button( frame_left, text="Go back to start page", command=lambda: master.switch_frame(StartPage)).pack(side='bottom') self.frame1 = Frame(self) self.frame1.pack(fill='x') self.rectangle = tk.Canvas(self.frame1) self.rectangle.pack() self.create_ret(self.rectangle) # self.update_clock() self.master = master self.commencer_un_jeu() def create_circle(self, r, canvasName, color): #center coordinates, radius x = random.randint(20, 300) y = random.randint(20, 250) x0 = x - r y0 = y - r x1 = x + r y1 = y + r return canvasName.create_oval(x0, y0, x1, y1, fill=color) def create_ret(self, canvas): return canvas.create_rectangle(0, 500, 500, 0, fill="#fdffdb") def update_clock(self): self.temps_de_rect = (time.time() - self.debut) self.temps_de_rect = time.strftime("%H:%M:%S", time.gmtime(self.temps_de_rect)) self.label.configure(text=self.temps_de_rect) if self.fin: self.master.after(1000, self.update_clock) def commencer_un_jeu(self): self.fin = True try: self.rejouer.destroy() self.label.config(text='') self.Nombre_2.delete(0, END) self.Nombre_1.delete(0, END) except: pass self.bt_valider = tk.Button(self.frame_left, text='valider', command=lambda: self.fin_du_jeu()) self.bt_valider.pack(side='top', anchor='w') self.debut = time.time() self.temps_de_rect = (time.time() - self.debut) self.temps_de_rect = time.strftime("%H:%M:%S", time.gmtime(self.temps_de_rect)) self.label.configure(text=self.temps_de_rect) self.update_clock() self.rectangle.destroy() self.rectangle = tk.Canvas(self.frame1) self.rectangle.pack() self.create_ret(self.rectangle) self.nombre_j1 = random.randint(1, 10) self.nombre_j2 = random.randint(1, 10) for _ in range(self.nombre_j2): self.create_circle(20, self.rectangle, 'red') for _ in range(self.nombre_j1): self.create_circle(20, self.rectangle, 'blue') def fin_du_jeu(self): self.fin = False if (int(self.Nombre_1.get()) == self.nombre_j1) and (int( self.Nombre_2.get()) == self.nombre_j2): #jeu gagné self.bt_valider.destroy() self.rejouer = Button(self.frame_left, text="Rejouer", command=lambda: self.commencer_un_jeu()) self.rejouer.pack(side='top', fill='x') self.temps_de_rect = (time.time() - self.debut) self.temps_de_rect = time.strftime("%H:%M:%S", time.gmtime(self.temps_de_rect)) self.label.configure(text=self.temps_de_rect) self.rectangle.create_text(200, 150, fill="darkblue", font="Times 20 italic bold", text="Victoire") else: self.bt_valider.destroy() self.rejouer = Button(self.frame_left, text="Rejouer", command=lambda: self.commencer_un_jeu()) self.rejouer.pack(side='top', fill='x') self.temps_de_rect = (time.time() - self.debut) self.temps_de_rect = time.strftime("%H:%M:%S", time.gmtime(self.temps_de_rect)) self.label.configure(text=self.temps_de_rect) self.rectangle.create_text(200, 150, fill="darkblue", font="Times 20 italic bold", text="Defaite")
class AudioDemo(Frame): """ A demo application class that provides simple GUI for testing featurizer+classifier on microphone or wav file input. """ def __init__(self, featurizer_model=None, classifier_model=None, sample_rate=None, channels=None, input_device=None, categories=None, image_width=80, ignore_label=None, threshold=None, wav_file=None, clear=5): """ Initialize AudioDemo object featurizer_model - the path to the compiled ELL featurizer classifier_model - the path to the compiled ELL classifier sample_rate - sample rate to featurizer is expecting channels - number of channels featurizer is expecting input_device - optional id of microphone to use categories - path to file containing category labels image_width - width of the spectrogram image ignore_label - list of predictions to ignore (e.g. [0] ignores prediction 0) threshold - ignore predictions that have confidence below this number (e.g. 0.5) wav_file - optional wav_file to process when you click Play """ input_device super().__init__() self.CLASSIFIER_MODEL_KEY = "classifier_model" self.FEATURIZER_MODEL_KEY = "featurizer_model" self.WAV_FILE_KEY = "wav_file" self.CATEGORY_FILE_KEY = "categories" self.get_settings_file_name() self.load_settings() self.reading_input = False self.featurizer_model = None if featurizer_model: self.featurizer_model = featurizer_model self.settings[self.FEATURIZER_MODEL_KEY] = featurizer_model elif self.FEATURIZER_MODEL_KEY in self.settings: self.featurizer_model = self.settings[self.FEATURIZER_MODEL_KEY] self.classifier_model = None if classifier_model: self.classifier_model = classifier_model self.settings[self.CLASSIFIER_MODEL_KEY] = classifier_model elif self.CLASSIFIER_MODEL_KEY in self.settings: self.classifier_model = self.settings[self.CLASSIFIER_MODEL_KEY] self.wav_filename = wav_file if self.wav_filename is None and self.WAV_FILE_KEY in self.settings: self.wav_filename = self.settings[self.WAV_FILE_KEY] self.wav_file_list = None self.sample_rate = sample_rate if sample_rate is not None else 16000 self.channels = channels if channels is not None else 1 self.input_device = input_device self.num_classifier_features = None if not categories and self.CATEGORY_FILE_KEY in self.settings: categories = self.settings[self.CATEGORY_FILE_KEY] self.categories = categories if categories: self.settings[self.CATEGORY_FILE_KEY] = categories self.save_settings() # in case we just changed it. self.min_value = 0.0 self.max_value = 1.0 self.update_minmax = True self.ignore_list = [] if ignore_label: self.ignore_list = [ ignore_label ] self.threshold = threshold self.output_clear_time = int(clear * 1000) if clear else 5000 self.featurizer = None self.classifier = None self.wav_file = None self.speaker = None self.microphone = None self.animation = None self.show_spectrogram = True self.colormap_name = "plasma" self.show_classifier_output = True self.last_prediction = None self.probability = 0 # Threads self.read_input_thread = None self.lock = Lock() self.main_thread = get_ident() self.message_queue = [] # UI components self.max_spectrogram_width = image_width self.features_entry = None self.spectrogram_image = None self.classifier_feature_data = None self.spectrogram_image_data = None self.init_ui() if self.featurizer_model: self.load_featurizer_model(os.path.abspath(self.featurizer_model)) else: self.show_output("Please specify and load a feature model") if self.classifier_model: self.load_classifier(self.classifier_model) self.setup_spectrogram_image() else: self.show_output("Please specify and load a classifier model") def get_settings_file_name(self): """ this app stores the various UI field values in a settings file in your temp folder so you don't always have to specify the full command line options """ import tempfile temp = tempfile.gettempdir() self.settings_file_name = os.path.join(temp, "ELL", "Audio", "viewaudio.json") def load_settings(self): """ load the previously saved settings from disk, if any """ self.settings = {} if os.path.isfile(self.settings_file_name): with open(self.settings_file_name, "r") as f: self.settings = json.load(f) def save_settings(self): """ save the current settings to disk """ settings_dir = os.path.dirname(self.settings_file_name) if not os.path.isdir(settings_dir): os.makedirs(settings_dir) with open(self.settings_file_name, "w") as f: f.write(json.dumps(self.settings)) def load_featurizer_model(self, featurizer_model): """ load the given compiled ELL featurizer for use in processing subsequent audio input """ if featurizer_model: self.featurizer = featurizer.AudioTransform(featurizer_model, 40) self.setup_spectrogram_image() self.show_output("Feature input size: {}, output size: {}".format( self.featurizer.input_size, self.featurizer.output_size)) if self.features_entry.get() != featurizer_model: self.features_entry.delete(0, END) self.features_entry.insert(0, featurizer_model) self.init_data() def load_classifier(self, classifier_path): """ load the given compiled ELL classifier for use in processing subsequent audio input """ if classifier_path: self.classifier = classifier.AudioClassifier(classifier_path, self.categories, self.ignore_list, self.threshold) self.show_output("Classifier input size: {}, output size: {}".format( self.classifier.input_size, self.classifier.output_size)) if self.classifier_entry.get() != classifier_path: self.classifier_entry.delete(0, END) self.classifier_entry.insert(0, classifier_path) self.init_data() def init_data(self): """ initialize the spectrogram_image_data and classifier_feature_data based on the newly loaded model info """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) if self.spectrogram_image is not None: self.spectrogram_image.set_data(self.spectrogram_image_data) if self.classifier: self.num_classifier_features = self.classifier.input_size // self.featurizer.output_size dim = (self.num_classifier_features, self.featurizer.output_size) self.classifier_feature_data = np.zeros(dim, dtype=float) def accumulate_feature(self, feature_data): """ accumulate the feature data and pass feature data to classifier """ if self.classifier and self.show_classifier_output: self.classifier_feature_data = np.vstack((self.classifier_feature_data, feature_data))[-self.num_classifier_features:,:] self.evaluate_classifier() def accumulate_spectrogram_image(self, feature_data): """ accumulate the feature data into the spectrogram image """ image_data = self.spectrogram_image_data feature_data = np.reshape(feature_data, [-1,1]) new_image = np.hstack((image_data, feature_data))[:,-image_data.shape[1]:] image_data[:,:] = new_image def set_spectrogram_image(self): """ update the spectrogram image and the min/max values """ self.lock.acquire() # protect access to the shared state if self.update_minmax and self.show_spectrogram: min_value = np.min(self.spectrogram_image_data) max_value = np.max(self.spectrogram_image_data) if np.isfinite(min_value) and np.isfinite(max_value): self.min_value = min_value self.max_value = max_value eps = 0.1 if self.max_value - self.min_value < eps: self.max_value = self.min_value + eps self.spectrogram_image.set_clim(self.min_value, self.max_value) self.spectrogram_image.set_data(self.spectrogram_image_data) self.lock.release() def get_correct_shape(self, shape): """ for some reason keras stores input shape as (None,80,40), and numpy hates that so we have to change this to (1,80,40) """ shape = list(shape) fix = [x if x else 1 for x in shape] return tuple(fix) def clear_output(self): """ remove some of the Output based a the timeout callback """ self.output_text.delete(1.0, 2.0) def process_output(self): """ show output that was queued by background thread """ self.lock.acquire() messages = self.message_queue self.message_queue = [] self.lock.release() for msg in messages: self.show_output(msg) def show_output(self, message): """ show output message, or queue it if we are on a background thread """ if self.main_thread != get_ident(): self.message_queue += [message] return for line in str(message).split('\n'): self.output_text.insert(END, "{}\n".format(line)) self.output_text.see("end") # scroll to end self.after(self.output_clear_time, self.clear_output) def evaluate_classifier(self): """ run the classifier model on the current feature data and show the prediction, if any """ if self.evaluate_classifier and self.classifier and self.classifier_feature_data is not None: prediction, probability, label = self.classifier.predict(self.classifier_feature_data.ravel()) if prediction is not None: percent = int(100*probability) if self.last_prediction != prediction or self.probability < probability: self.last_prediction = prediction self.probability = probability self.show_output("<<< DETECTED ({}) {}% {} >>>".format(prediction, percent, label)) def start_playing(self, filename): """ Play a wav file, and classify the audio. Note we use a background thread to read the wav file and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the audio playback """ if self.speaker is None: self.speaker = speaker.Speaker() self.stop() self.reading_input = False self.wav_file = wav_reader.WavReader(self.sample_rate, self.channels) self.wav_file.open(filename, self.featurizer.input_size, self.speaker) def update_func(frame_index): self.process_output() if not self.reading_input: self.after(1, self.on_stopped) self.set_spectrogram_image() return (self.spectrogram_image,) if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) (30 fps is usually fine) self.animation = animation.FuncAnimation(self.features_figure, update_func, interval=33, blit=True) # start background thread to read and classify the audio. self.featurizer.open(self.wav_file) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def start_recording(self): """ Start recording audio from the microphone nd classify the audio. Note we use a background thread to process the audio and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the microphone readings """ if self.microphone is None: self.microphone = microphone.Microphone(False) self.stop() num_channels = 1 self.microphone.open(self.featurizer.input_size, self.sample_rate, num_channels, self.input_device) def update_func(frame_index): # this is an animation callback to update the UI every 33 milliseconds. self.process_output() self.set_spectrogram_image() if not self.reading_input: self.after(1, self.on_stopped) return (self.spectrogram_image,) if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) (30 fps is usually fine) self.animation = animation.FuncAnimation(self.features_figure, update_func, interval=33, blit=True) # start background thread to read and classify the recorded audio. self.featurizer.open(self.microphone) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def on_read_features(self): """ this is the background thread entry point. So we read the feature data in a loop and pass it to the classifier """ try: while self.reading_input and self.featurizer: feature_data = self.featurizer.read() if feature_data is None: break # eof else: self.lock.acquire() self.accumulate_feature(feature_data) if self.show_spectrogram: self.accumulate_spectrogram_image(feature_data) self.lock.release() except: errorType, value, traceback = sys.exc_info() print("### Exception reading input: " + str(errorType) + ": " + str(value) + " " + str(traceback)) while traceback: print(traceback.tb_frame.f_code) traceback = traceback.tb_next self.reading_input = False def stop(self): """ called when user clicks the stop button, or we reach the end of a wav file input """ # close streams if self.animation: self.animation.event_source.stop() self.animation = None if self.microphone: self.microphone.close() if self.speaker: self.speaker.close() if self.wav_file: self.wav_file.close() self.wav_file = None self.reading_input = False self.last_prediction = None self.probability = 0 def on_rec_button_click(self): """ called when user clicks the record button, same button is used to "stop" recording. """ if self.rec_button["text"] == "Rec": self.rec_button["text"] = "Stop" self.play_button["text"] = "Play" self.start_recording() else: self.rec_button["text"] = "Rec" self.on_stop() def on_play_button_click(self): """ called when user clicks the record button, same button is used to "stop" playback """ if self.play_button["text"] == "Play": self.play_button["text"] = "Stop" self.rec_button["text"] = "Rec" self.on_play() else: self.play_button["text"] = "Play" self.on_stop() def on_play(self): """ called when user clicks the Play button """ filename = self.wav_filename_entry.get() filename = filename.strip('"') self.wav_filename = filename self.settings[self.WAV_FILE_KEY] = filename self.save_settings() self.start_playing(filename) def on_stop(self): """ called when user clicks the Stop button """ self.reading_input = False if self.wav_file: self.wav_file.close() self.wav_file = None if self.read_input_thread: self.read_input_thread.join() self.read_input_thread = None self.stop() def on_stopped(self): """ called when we reach the end of the wav file playback """ self.play_button["text"] = "Play" self.stop() def get_wav_list(self): if self.wav_filename and os.path.isfile(self.wav_filename): dir_name = os.path.dirname(self.wav_filename) if not self.wav_file_list: self.wav_file_list = [x for x in os.listdir(dir_name) if os.path.splitext(x)[1] == ".wav"] self.wav_file_list.sort() return self.wav_file_list def select_wav_file(self, filename): self.wav_filename = filename # show the file in the UI self.wav_filename_entry.delete(0, END) if self.wav_filename: self.wav_filename_entry.insert(0, self.wav_filename) # and automatically play the file. self.on_play() def on_minus_key(self, event): """ When user presses the plus button we reverse to the previous wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i - 1 >= 0: next_wav_file = self.wav_file_list[i - 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def on_plus_key(self, event): """ When user presses the plus button we advance to the next wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i + 1 < len(self.wav_file_list): next_wav_file = self.wav_file_list[i + 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def init_ui(self): """ setup the GUI for the app """ self.master.title("Test") self.pack(fill=BOTH, expand=True) # Input section input_frame = LabelFrame(self, text="Input") input_frame.bind("-", self.on_minus_key) input_frame.bind("+", self.on_plus_key) input_frame.pack(fill=X) self.play_button = Button(input_frame, text="Play", command=self.on_play_button_click) self.play_button.pack(side=RIGHT, padx=4) self.rec_button = Button(input_frame, text="Rec", command=self.on_rec_button_click) self.rec_button.pack(side=RIGHT, padx=4) self.wav_filename_entry = Entry(input_frame, width=24) self.wav_filename_entry.pack(fill=X) self.wav_filename_entry.delete(0, END) if self.wav_filename: self.wav_filename_entry.insert(0, self.wav_filename) # Feature section features_frame = LabelFrame(self, text="Features") features_frame.pack(fill=X) features_control_frame = Frame(features_frame) features_control_frame.pack(fill=X) load_features_button = Button(features_control_frame, text="Load", command=self.on_load_featurizer_model) load_features_button.pack(side=RIGHT) self.features_entry = Entry(features_control_frame, width=8) self.features_entry.pack(fill=X) self.features_entry.delete(0, END) if self.featurizer_model: self.features_entry.insert(0, self.featurizer_model) viz_frame = Frame(features_frame) viz_frame.pack(fill=X) self.features_figure = Figure(figsize=(5, 4), dpi=100) self.subplot = self.features_figure.add_subplot(111) canvas = FigureCanvasTkAgg(self.features_figure, master=viz_frame) canvas.draw() canvas.show() canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=True) # Classifier section classifier_frame = LabelFrame(self, text="Classifier") classifier_frame.pack(fill=X) load_classifier_button = Button(classifier_frame, text="Load", command=self.on_load_classifier) load_classifier_button.pack(side=RIGHT) self.classifier_entry = Entry(classifier_frame, width=8) self.classifier_entry.pack(fill=X) self.classifier_entry.delete(0, END) if self.classifier_model: self.classifier_entry.insert(0, self.classifier_model) # Output section output_frame = LabelFrame(self, text="Output") output_frame.pack(fill=BOTH, expand=True) self.output_text = Text(output_frame) self.output_text.pack(fill=BOTH, padx=4, expand=True) def setup_spectrogram_image(self): """ this need to be called if you load a new feature model, because the featurizer output size might have changed. """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) self.subplot.clear() self.spectrogram_image = self.subplot.imshow(self.spectrogram_image_data, vmin=self.min_value, vmax=self.max_value, origin="lower", animated=True, cmap=pyplot.get_cmap(self.colormap_name)) def on_load_featurizer_model(self): """ called when user clicks the Load button for the feature model """ filename = self.features_entry.get() filename = filename.strip('"') self.settings[self.FEATURIZER_MODEL_KEY] = filename self.save_settings() self.stop() self.load_featurizer_model(filename) def on_load_classifier(self): """ called when user clicks the Load button for the feature model """ self.classifier_model = self.classifier_entry.get() self.settings[self.CLASSIFIER_MODEL_KEY] = self.classifier_model self.save_settings() self.stop() self.load_classifier(self.classifier_model)
class App(Tk): #the main class for the main window def __init__(self): Tk.__init__(self) # window properties self.title(string="Screen Recorder") self.iconbitmap("icon.ico") self.resizable(width=False, height=False) ffmpegAvailable = False for item in os.listdir(): if item == "ffmpeg.exe": ffmpegAvailable = True break if not ffmpegAvailable: self.withdraw() if messagebox.askyesno( "FFmpeg Not Found", "ffmpeg.exe could not be found in screen recorder's directory. Do you want to be redirected to the ffmpeg download website?" ): webbrowser.open_new_tab("https://ffmpeg.zeranoe.com/builds/") exit() self.cmdGen = cmdGen( ) # create a command generator object to store settings # file name label1 = Label(self, text="File Name:") label1.grid(row=0, column=0, sticky="") self.entry1 = Entry(self) self.entry1.grid(row=0, column=1, sticky="ew") # ensure the existance of the "ScreenCaptures" directory try: os.mkdir("ScreenCaptures") except FileExistsError: pass os.chdir("ScreenCaptures") # find a default file name that is currently available. defaultFile = "ScreenCapture.mp4" available = False fileNum = 0 while available == False: hasMatch = False for item in os.listdir(): if item == defaultFile: hasMatch = True break if not hasMatch: available = True else: fileNum += 1 defaultFile = "ScreenCapture" + str(fileNum) + ".mp4" os.chdir("..") self.entry1.insert(END, defaultFile) # radio buttons determine what to record self.what = StringVar() self.what.set("desktop") self.radio2 = Radiobutton(self, text="record the window with the title of: ", variable=self.what, value="title", command=self.enDis1) self.radio1 = Radiobutton(self, text="record the entire desktop", variable=self.what, value="desktop", command=self.enDis) self.radio1.grid(row=1, column=0, sticky="w") self.radio2.grid(row=2, column=0, sticky="w") self.entry2 = Entry(self, state=DISABLED) self.entry2.grid(row=2, column=1, sticky="ew") # initialize webcam self.webcamdevices = Webcam.listCam() self.webcamrecorder = Webcam.capturer("") # "record from webcam" checkbox self.rcchecked = IntVar() self.recordcam = Checkbutton(self, text="Record from webcam", command=self.checkboxChanged, variable=self.rcchecked) self.recordcam.grid(row=3, column=0) # a drop-down allowing you to select the webcam device from the available directshow capture devices self.devicename = StringVar(self) if self.webcamdevices: self.devicename.set(self.webcamdevices[0]) self.deviceselector = OptionMenu(self, self.devicename, *self.webcamdevices) self.deviceselector.config(state=DISABLED) self.deviceselector.grid(row=3, column=1) else: self.devicename.set("NO DEVICES AVAILABLE") self.recordcam.config(state=DISABLED) self.deviceselector = OptionMenu(self, self.devicename, "NO DEVICES AVAILABLE") self.deviceselector.config(state=DISABLED) self.deviceselector.grid(row=3, column=1) self.opButton = Button(self, text="⚙ Additional Options...", command=self.openSettings) self.opButton.grid(row=4, column=1, sticky='e') # the "start recording" button self.startButton = Button(self, text="⏺ Start Recording", command=self.startRecord) self.startButton.grid(row=5, column=0, columnspan=2) # some variables self.recording = False # are we recording? self.proc = None # the popen object for ffmpeg (during screenrecord) self.recorder = recordFile.recorder( ) # the "recorder" object for audio (see recordFile.py) self.mergeProcess = None # the popen object for ffmpeg (while merging video and audio files) # start the ffmpeg monitoring callback self.pollClosed() def openSettings(self): self.settings = settingsWin(self, self.cmdGen, self.recorder) def pollClosed(self): """callback that repeats itself every 100ms. Automatically determines if ffmpeg is still running.""" if self.recording: if self.proc.poll() != None: self.startRecord() messagebox.showerror( "ffmpeg error", "ffmpeg has stopped working. ERROR: \n" + str(self.proc.stderr.read()).replace('\\r\\n', '\n')) if self.recorder.error: self.startRecord() if self.mergeProcess and self.recording == False: if self.mergeProcess.poll() != None: self.startButton.config(text="⏺ Start Recording", state=NORMAL) self.title(string="Screen Recorder") self.after(100, self.pollClosed) def enDis(self): """Called when the "desktop" radio button is pressed""" self.entry2.config(state=DISABLED) # self.what.set("desktop") def enDis1(self): """Called when the "window title" radio button is pressed""" self.entry2.config(state=NORMAL) # self.what.set("title") def checkboxChanged(self): """Called when the "record webcam" checkbox is checked or unchecked.""" #self.rcchecked = not self.rcchecked if self.rcchecked.get(): self.deviceselector.config(state=NORMAL) else: self.deviceselector.config(state=DISABLED) def startRecord(self): """toggles recording. Will start conversion subprocess on recording completion""" if self.recording == False: # change the window self.title(string="Screen Recorder (Recording...)") self.startButton.config(text="⏹️ Stop Recording") self.filename = self.entry1.get() # disable interface self.entry1.config(state=DISABLED) self.radio1.config(state=DISABLED) self.radio2.config(state=DISABLED) self.deviceselector.config(state=DISABLED) self.opButton.config(state=DISABLED) if self.what.get() == "title": self.entry2.config(state=DISABLED) # ensure the existence of the "tmp" directory try: os.mkdir("tmp") except FileExistsError: pass # start screen recording process self.recording = True startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE # self.cmdGen.setFps(60) # self.cmdGen.setEncode('nvenc_h264') # CPU: mpeg4 // NVIDIA: h264_nvenc // AMD: no. self.cmdGen.setSource(self.what.get() == "title", self.entry2.get()) command = self.cmdGen.getCmd("tmp/tmp.mkv") self.proc = subprocess.Popen(args=command, startupinfo=startupinfo, stderr=subprocess.PIPE) # start audio recording self.recorder.record("tmp/tmp.wav") # start webcam recording, if checked self.recordcam.config(state=DISABLED) if self.rcchecked.get() and self.webcamdevices: self.webcamrecorder.setDevice(str(self.devicename.get())) self.webcamrecorder.startCapture("tmp/webcamtmp.mkv") # minimize the window to get it out of the way of the recording self.iconify() elif self.recording == True: self.deiconify() defaultFile = self.filename # re-enable interface self.entry1.config(state=NORMAL) self.radio1.config(state=NORMAL) self.radio2.config(state=NORMAL) self.opButton.config(state=NORMAL) if self.webcamdevices: self.recordcam.config(state=NORMAL) if self.rcchecked.get(): self.deviceselector.config(state=NORMAL) if self.what.get() == "title": self.entry2.config(state=NORMAL) available = False fileNum = 0 # stop all recording processes self.recording = False self.proc.terminate() self.recorder.stop_recording() if self.rcchecked.get() and self.webcamdevices: self.webcamrecorder.stopCapture() try: os.mkdir("ScreenCaptures") except FileExistsError: pass # change the window title and button text to reflect the current process self.title(string="Screen Recorder (converting...)") self.startButton.config( text="converting your previous recording, please wait...", state=DISABLED) # start the video conversion process startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = subprocess.SW_HIDE self.cmdGen.config(audList=self.recorder.devices) command = self.cmdGen.getCvtCmd("ScreenCaptures/" + self.filename) if not self.recorder.error: self.mergeProcess = subprocess.Popen(args=command, startupinfo=startupinfo) # if self.rcchecked.get(): # self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav','-i','tmp/webcamtmp.mkv','-filter_complex','[2:v] scale=640:-1 [inner]; [0:0][inner] overlay=0:0 [out]',"-shortest",'-map','[out]','-y',"ScreenCaptures/"+self.filename]) # else: # self.mergeProcess = subprocess.Popen(args= ["ffmpeg","-i",'tmp/tmp.mkv','-i','tmp/tmp.wav',"-shortest",'-y',"ScreenCaptures/"+self.filename], startupinfo=startupinfo) # change the screen capture name to something that is not taken os.chdir("ScreenCaptures") while True: matches = 0 for item in os.listdir(): if item == defaultFile: matches += 1 if matches == 0: self.entry1.delete(0, END) self.entry1.insert(END, defaultFile) break else: fileNum += 1 file = self.filename.split(".") defaultFile = file[0].rstrip("1234567890") + str( fileNum) + "." + file[1] os.chdir("../")
class Sync(Tk): """FolderSync main window.""" def __init__(self): Tk.__init__(self, className='FolderSync') self.title("FolderSync") self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.protocol("WM_DELETE_WINDOW", self.quitter) self.icon = PhotoImage(master=self, file=IM_ICON) self.iconphoto(True, self.icon) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) # --- icons self.img_about = PhotoImage(master=self, file=IM_ABOUT) self.img_open = PhotoImage(master=self, file=IM_OPEN) self.img_plus = PhotoImage(master=self, file=IM_PLUS) self.img_moins = PhotoImage(master=self, file=IM_MOINS) self.img_sync = PhotoImage(master=self, file=IM_SYNC) self.img_prev = PhotoImage(master=self, file=IM_PREV) self.img_expand = PhotoImage(master=self, file=IM_EXPAND) self.img_collapse = PhotoImage(master=self, file=IM_COLLAPSE) self.original = "" self.sauvegarde = "" # list of files / folders to delete before starting the copy because # they are not of the same type on the original and the backup self.pb_chemins = [] self.err_copie = False self.err_supp = False # --- init log files l = [f for f in listdir(PATH) if match(r"foldersync[0-9]+.pid", f)] nbs = [] for f in l: with open(join(PATH, f)) as fich: old_pid = fich.read().strip() if exists("/proc/%s" % old_pid): nbs.append(int(search(r"[0-9]+", f).group())) else: remove(join(PATH, f)) if not nbs: i = 0 else: nbs.sort() i = 0 while i in nbs: i += 1 self.pidfile = PID_FILE % i open(self.pidfile, 'w').write(str(getpid())) self.log_copie = LOG_COPIE % i self.log_supp = LOG_SUPP % i self.logger_copie = setup_logger("copie", self.log_copie) self.logger_supp = setup_logger("supp", self.log_supp) date = datetime.now().strftime('%d/%m/%Y %H:%M') self.logger_copie.info("\n### %s ###\n" % date) self.logger_supp.info("\n### %s ###\n" % date) # --- filenames and extensions that will not be copied exclude_list = split(r'(?<!\\) ', CONFIG.get("Defaults", "exclude_copie")) self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[1:]) else: self.exclude_names.append(elt.replace("\ ", " ")) # --- paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] # while "" in self.exclude_path_supp: # self.exclude_path_supp.remove("") self.q_copie = Queue() self.q_supp = Queue() # True if a copy / deletion is running self.is_running_copie = False self.is_running_supp = False self.style = Style(self) self.style.theme_use("clam") self.style.configure("TProgressbar", troughcolor='lightgray', background='#387EF5', lightcolor="#5D95F5", darkcolor="#2758AB") self.style.map("TProgressbar", lightcolor=[("disabled", "white")], darkcolor=[("disabled", "gray")]) self.style.configure("folder.TButton", padding=0) # --- menu self.menu = Menu(self, tearoff=False) self.configure(menu=self.menu) # -------- recents self.menu_recent = Menu(self.menu, tearoff=False) if RECENT: for ch_o, ch_s in RECENT: self.menu_recent.add_command( label="%s -> %s" % (ch_o, ch_s), command=lambda o=ch_o, s=ch_s: self.open(o, s)) else: self.menu.entryconfigure(0, state="disabled") # -------- favorites self.menu_fav = Menu(self.menu, tearoff=False) self.menu_fav_del = Menu(self.menu_fav, tearoff=False) self.menu_fav.add_command(label=_("Add"), image=self.img_plus, compound="left", command=self.add_fav) self.menu_fav.add_cascade(label=_("Remove"), image=self.img_moins, compound="left", menu=self.menu_fav_del) for ch_o, ch_s in FAVORIS: label = "%s -> %s" % (ch_o, ch_s) self.menu_fav.add_command( label=label, command=lambda o=ch_o, s=ch_s: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") # -------- log files menu_log = Menu(self.menu, tearoff=False) menu_log.add_command(label=_("Copy"), command=self.open_log_copie) menu_log.add_command(label=_("Removal"), command=self.open_log_suppression) # -------- settings menu_params = Menu(self.menu, tearoff=False) self.copy_links = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "copy_links")) self.show_size = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "show_size")) menu_params.add_checkbutton(label=_("Copy links"), variable=self.copy_links, command=self.toggle_copy_links) menu_params.add_checkbutton(label=_("Show total size"), variable=self.show_size, command=self.toggle_show_size) self.langue = StringVar(self, CONFIG.get("Defaults", "language")) menu_lang = Menu(menu_params, tearoff=False) menu_lang.add_radiobutton(label="English", value="en", variable=self.langue, command=self.change_language) menu_lang.add_radiobutton(label="Français", value="fr", variable=self.langue, command=self.change_language) menu_params.add_cascade(label=_("Language"), menu=menu_lang) menu_params.add_command(label=_("Exclude from copy"), command=self.exclusion_copie) menu_params.add_command(label=_("Exclude from removal"), command=self.exclusion_supp) self.menu.add_cascade(label=_("Recents"), menu=self.menu_recent) self.menu.add_cascade(label=_("Favorites"), menu=self.menu_fav) self.menu.add_cascade(label=_("Log"), menu=menu_log) self.menu.add_cascade(label=_("Settings"), menu=menu_params) self.menu.add_command(image=self.img_prev, compound="center", command=self.list_files_to_sync) self.menu.add_command(image=self.img_sync, compound="center", state="disabled", command=self.synchronise) self.menu.add_command(image=self.img_about, compound="center", command=lambda: About(self)) # --- tooltips wrapper = TooltipMenuWrapper(self.menu) wrapper.add_tooltip(4, _('Preview')) wrapper.add_tooltip(5, _('Sync')) wrapper.add_tooltip(6, _('About')) # --- path selection frame_paths = Frame(self) frame_paths.grid(row=0, sticky="ew", pady=(10, 0)) frame_paths.columnconfigure(0, weight=1) frame_paths.columnconfigure(1, weight=1) f1 = Frame(frame_paths, height=26) f2 = Frame(frame_paths, height=26) f1.grid(row=0, column=0, sticky="ew") f2.grid(row=0, column=1, sticky="ew") f1.grid_propagate(False) f2.grid_propagate(False) f1.columnconfigure(1, weight=1) f2.columnconfigure(1, weight=1) # -------- path to original Label(f1, text=_("Original")).grid(row=0, column=0, padx=(10, 4)) f11 = Frame(f1) f11.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_orig = Entry(f11) self.entry_orig.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_orig = Button(f1, image=self.img_open, style="folder.TButton", command=self.open_orig) self.b_open_orig.grid(row=0, column=2, padx=(0, 7)) # -------- path to backup Label(f2, text=_("Backup")).grid(row=0, column=0, padx=(8, 4)) f22 = Frame(f2) f22.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_sauve = Entry(f22) self.entry_sauve.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_sauve = Button(f2, image=self.img_open, width=2, style="folder.TButton", command=self.open_sauve) self.b_open_sauve.grid(row=0, column=5, padx=(0, 10)) paned = PanedWindow(self, orient='horizontal') paned.grid(row=2, sticky="eswn") paned.rowconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.columnconfigure(0, weight=1) # --- left side frame_left = Frame(paned) paned.add(frame_left, weight=1) frame_left.rowconfigure(3, weight=1) frame_left.columnconfigure(0, weight=1) # -------- files to copy f_left = Frame(frame_left) f_left.columnconfigure(2, weight=1) f_left.grid(row=2, columnspan=2, pady=(4, 2), padx=(10, 4), sticky="ew") Label(f_left, text=_("To copy")).grid(row=0, column=2) frame_copie = Frame(frame_left) frame_copie.rowconfigure(0, weight=1) frame_copie.columnconfigure(0, weight=1) frame_copie.grid(row=3, column=0, sticky="eswn", columnspan=2, pady=(2, 4), padx=(10, 4)) self.tree_copie = CheckboxTreeview(frame_copie, selectmode='none', show='tree') self.b_expand_copie = Button(f_left, image=self.img_expand, style="folder.TButton", command=self.tree_copie.expand_all) TooltipWrapper(self.b_expand_copie, text=_("Expand all")) self.b_expand_copie.grid(row=0, column=0) self.b_expand_copie.state(("disabled", )) self.b_collapse_copie = Button(f_left, image=self.img_collapse, style="folder.TButton", command=self.tree_copie.collapse_all) TooltipWrapper(self.b_collapse_copie, text=_("Collapse all")) self.b_collapse_copie.grid(row=0, column=1, padx=4) self.b_collapse_copie.state(("disabled", )) self.tree_copie.tag_configure("warning", foreground="red") self.tree_copie.tag_configure("link", font="tkDefaultFont 9 italic", foreground="blue") self.tree_copie.tag_bind("warning", "<Button-1>", self.show_warning) self.tree_copie.grid(row=0, column=0, sticky="eswn") self.scroll_y_copie = Scrollbar(frame_copie, orient="vertical", command=self.tree_copie.yview) self.scroll_y_copie.grid(row=0, column=1, sticky="ns") self.scroll_x_copie = Scrollbar(frame_copie, orient="horizontal", command=self.tree_copie.xview) self.scroll_x_copie.grid(row=1, column=0, sticky="ew") self.tree_copie.configure(yscrollcommand=self.scroll_y_copie.set, xscrollcommand=self.scroll_x_copie.set) self.pbar_copie = Progressbar(frame_left, orient="horizontal", mode="determinate") self.pbar_copie.grid(row=4, columnspan=2, sticky="ew", padx=(10, 4), pady=4) self.pbar_copie.state(("disabled", )) # --- right side frame_right = Frame(paned) paned.add(frame_right, weight=1) frame_right.rowconfigure(3, weight=1) frame_right.columnconfigure(0, weight=1) # -------- files to delete f_right = Frame(frame_right) f_right.columnconfigure(2, weight=1) f_right.grid(row=2, columnspan=2, pady=(4, 2), padx=(4, 10), sticky="ew") Label(f_right, text=_("To remove")).grid(row=0, column=2) frame_supp = Frame(frame_right) frame_supp.rowconfigure(0, weight=1) frame_supp.columnconfigure(0, weight=1) frame_supp.grid(row=3, columnspan=2, sticky="eswn", pady=(2, 4), padx=(4, 10)) self.tree_supp = CheckboxTreeview(frame_supp, selectmode='none', show='tree') self.b_expand_supp = Button(f_right, image=self.img_expand, style="folder.TButton", command=self.tree_supp.expand_all) TooltipWrapper(self.b_expand_supp, text=_("Expand all")) self.b_expand_supp.grid(row=0, column=0) self.b_expand_supp.state(("disabled", )) self.b_collapse_supp = Button(f_right, image=self.img_collapse, style="folder.TButton", command=self.tree_supp.collapse_all) TooltipWrapper(self.b_collapse_supp, text=_("Collapse all")) self.b_collapse_supp.grid(row=0, column=1, padx=4) self.b_collapse_supp.state(("disabled", )) self.tree_supp.grid(row=0, column=0, sticky="eswn") self.scroll_y_supp = Scrollbar(frame_supp, orient="vertical", command=self.tree_supp.yview) self.scroll_y_supp.grid(row=0, column=1, sticky="ns") self.scroll_x_supp = Scrollbar(frame_supp, orient="horizontal", command=self.tree_supp.xview) self.scroll_x_supp.grid(row=1, column=0, sticky="ew") self.tree_supp.configure(yscrollcommand=self.scroll_y_supp.set, xscrollcommand=self.scroll_x_supp.set) self.pbar_supp = Progressbar(frame_right, orient="horizontal", mode="determinate") self.pbar_supp.grid(row=4, columnspan=2, sticky="ew", padx=(4, 10), pady=4) self.pbar_supp.state(("disabled", )) # --- bindings self.entry_orig.bind("<Key-Return>", self.list_files_to_sync) self.entry_sauve.bind("<Key-Return>", self.list_files_to_sync) def exclusion_supp(self): excl = ExclusionsSupp(self) self.wait_window(excl) # paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] def exclusion_copie(self): excl = ExclusionsCopie(self) self.wait_window(excl) exclude_list = CONFIG.get("Defaults", "exclude_copie").split(" ") self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[2:]) else: self.exclude_names.append(elt) def toggle_copy_links(self): CONFIG.set("Defaults", "copy_links", str(self.copy_links.get())) def toggle_show_size(self): CONFIG.set("Defaults", "show_size", str(self.show_size.get())) def open_log_copie(self): open_file(self.log_copie) def open_log_suppression(self): open_file(self.log_supp) def quitter(self): rep = True if self.is_running_copie or self.is_running_supp: rep = askokcancel( _("Confirmation"), _("A synchronization is ongoing, do you really want to quit?"), parent=self) if rep: self.destroy() def del_fav(self, nom): self.menu_fav.delete(nom) self.menu_fav_del.delete(nom) FAVORIS.remove(tuple(nom.split(" -> "))) save_config() if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") def add_fav(self): sauvegarde = self.entry_sauve.get() original = self.entry_orig.get() if original != sauvegarde and original and sauvegarde: if exists(original) and exists(sauvegarde): if not (original, sauvegarde) in FAVORIS: FAVORIS.append((original, sauvegarde)) save_config() label = "%s -> %s" % (original, sauvegarde) self.menu_fav.entryconfigure(1, state="normal") self.menu_fav.add_command(label=label, command=lambda o=original, s= sauvegarde: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) def open(self, ch_o, ch_s): self.entry_orig.delete(0, "end") self.entry_orig.insert(0, ch_o) self.entry_sauve.delete(0, "end") self.entry_sauve.insert(0, ch_s) self.list_files_to_sync() def open_sauve(self): sauvegarde = askdirectory(self.entry_sauve.get(), parent=self) if sauvegarde: self.entry_sauve.delete(0, "end") self.entry_sauve.insert(0, sauvegarde) def open_orig(self): original = askdirectory(self.entry_orig.get(), parent=self) if original: self.entry_orig.delete(0, "end") self.entry_orig.insert(0, original) def sync(self, original, sauvegarde): """ peuple tree_copie avec l'arborescence des fichiers d'original à copier vers sauvegarde et tree_supp avec celle des fichiers de sauvegarde à supprimer """ errors = [] copy_links = self.copy_links.get() excl_supp = [ path for path in self.exclude_path_supp if commonpath([path, sauvegarde]) == sauvegarde ] def get_name(elt): return elt.name.lower() def lower(char): return char.lower() def arbo(tree, parent, n): """ affiche l'arborescence complète de parent et renvoie la longueur maximale des items (pour gérer la scrollbar horizontale) """ m = 0 try: with scandir(parent) as content: l = sorted(content, key=get_name) for item in l: chemin = item.path nom = item.name if item.is_symlink(): if copy_links: tree.insert(parent, 'end', chemin, text=nom, tags=("whole", "link")) m = max(m, len(nom) * 9 + 20 * (n + 1)) elif ((nom not in self.exclude_names) and (splitext(nom)[-1] not in self.exclude_ext)): tree.insert(parent, 'end', chemin, text=nom, tags=("whole", )) m = max(m, len(nom) * 9 + 20 * (n + 1)) if item.is_dir(): m = max(m, arbo(tree, chemin, n + 1)) except NotADirectoryError: pass except Exception as e: errors.append(str(e)) return m def aux(orig, sauve, n, search_supp): m_copie = 0 m_supp = 0 try: lo = listdir(orig) ls = listdir(sauve) except Exception as e: errors.append(str(e)) lo = [] ls = [] lo.sort(key=lambda x: x.lower()) ls.sort(key=lambda x: x.lower()) supp = False copie = False if search_supp: for item in ls: chemin_s = join(sauve, item) if chemin_s not in excl_supp and item not in lo: supp = True self.tree_supp.insert(sauve, 'end', chemin_s, text=item, tags=("whole", )) m_supp = max(m_supp, int(len(item) * 9 + 20 * (n + 1)), arbo(self.tree_supp, chemin_s, n + 1)) for item in lo: chemin_o = join(orig, item) chemin_s = join(sauve, item) if ((item not in self.exclude_names) and (splitext(item)[-1] not in self.exclude_ext)): if item not in ls: # le dossier / fichier n'est pas dans la sauvegarde if islink(chemin_o): if copy_links: copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "link")) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1)))) else: copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", )) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1))), arbo(self.tree_copie, chemin_o, n + 1)) elif islink(chemin_o) and exists(chemin_o): # checking the existence prevent from copying broken links if copy_links: if not islink(chemin_s): self.pb_chemins.append(chemin_o) tags = ("whole", "warning", "link") else: tags = ("whole", "link") self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=tags) m_copie = max(m_copie, int(len(item) * 9 + 20 * (n + 1))) copie = True elif isfile(chemin_o): # first check if chemin_s is also a file if isfile(chemin_s): if getmtime(chemin_o) // 60 > getmtime( chemin_s) // 60: # le fichier f a été modifié depuis la dernière sauvegarde copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", )) else: self.pb_chemins.append(chemin_o) self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "warning")) elif isdir(chemin_o): # to avoid errors due to unrecognized item types (neither file nor folder nor link) if isdir(chemin_s): self.tree_copie.insert(orig, 'end', chemin_o, text=item) self.tree_supp.insert(sauve, 'end', chemin_s, text=item) c, s, mc, ms = aux( chemin_o, chemin_s, n + 1, search_supp and (chemin_s not in excl_supp)) supp = supp or s copie = copie or c if not c: # nothing to copy self.tree_copie.delete(chemin_o) else: m_copie = max( m_copie, mc, int(len(item) * 9 + 20 * (n + 1))) if not s: # nothing to delete self.tree_supp.delete(chemin_s) else: m_supp = max(m_supp, ms, int(len(item) * 9 + 20 * (n + 1))) else: copie = True self.pb_chemins.append(chemin_o) self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "warning")) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1))), arbo(self.tree_copie, chemin_o, n + 1)) return copie, supp, m_copie, m_supp self.tree_copie.insert("", 0, original, text=original, tags=("checked", ), open=True) self.tree_supp.insert("", 0, sauvegarde, text=sauvegarde, tags=("checked", ), open=True) c, s, mc, ms = aux(original, sauvegarde, 1, True) if not c: self.tree_copie.delete(original) self.tree_copie.column("#0", minwidth=0, width=0) else: mc = max(len(original) * 9 + 20, mc) self.tree_copie.column("#0", minwidth=mc, width=mc) if not s: self.tree_supp.delete(sauvegarde) self.tree_supp.column("#0", minwidth=0, width=0) else: ms = max(len(sauvegarde) * 9 + 20, mc) self.tree_supp.column("#0", minwidth=ms, width=ms) return errors def show_warning(self, event): if "disabled" not in self.b_open_orig.state(): x, y = event.x, event.y elem = event.widget.identify("element", x, y) if elem == "padding": orig = self.tree_copie.identify_row(y) sauve = orig.replace(self.original, self.sauvegarde) showwarning( _("Warning"), _("%(original)s and %(backup)s are not of the same kind (folder/file/link)" ) % { 'original': orig, 'backup': sauve }, master=self) def list_files_to_sync(self, event=None): """Display in a treeview the file to copy and the one to delete.""" self.pbar_copie.configure(value=0) self.pbar_supp.configure(value=0) self.sauvegarde = self.entry_sauve.get() self.original = self.entry_orig.get() if self.original != self.sauvegarde and self.original and self.sauvegarde: if exists(self.original) and exists(self.sauvegarde): o_s = (self.original, self.sauvegarde) if o_s in RECENT: RECENT.remove(o_s) self.menu_recent.delete("%s -> %s" % o_s) RECENT.insert(0, o_s) self.menu_recent.insert_command( 0, label="%s -> %s" % o_s, command=lambda o=self.original, s=self.sauvegarde: self. open(o, s)) if len(RECENT) == 10: del (RECENT[-1]) self.menu_recent.delete(9) save_config() self.menu.entryconfigure(0, state="normal") self.configure(cursor="watch") self.toggle_state_gui() self.update_idletasks() self.update() self.efface_tree() err = self.sync(self.original, self.sauvegarde) self.configure(cursor="") self.toggle_state_gui() c = self.tree_copie.get_children("") s = self.tree_supp.get_children("") if not (c or s): self.menu.entryconfigure(5, state="disabled") self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) elif not c: self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) elif not s: self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) if err: showerror(_("Errors"), "\n".join(err), master=self) notification_send(_("Scan is finished.")) warnings = self.tree_copie.tag_has('warning') if warnings: showwarning( _("Warning"), _("Some elements to copy (in red) are not of the same kind on the original and the backup." ), master=self) else: showerror(_("Error"), _("Invalid path!"), master=self) def efface_tree(self): """Clear both trees.""" c = self.tree_copie.get_children("") for item in c: self.tree_copie.delete(item) s = self.tree_supp.get_children("") for item in s: self.tree_supp.delete(item) self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) def toggle_state_gui(self): """Toggle the state (normal/disabled) of key elements of the GUI.""" if "disabled" in self.b_open_orig.state(): state = "!disabled" for i in range(7): self.menu.entryconfigure(i, state="normal") else: state = "disabled" for i in range(7): self.menu.entryconfigure(i, state="disabled") self.tree_copie.state((state, )) self.tree_supp.state((state, )) self.entry_orig.state((state, )) self.entry_sauve.state((state, )) self.b_expand_copie.state((state, )) self.b_collapse_copie.state((state, )) self.b_expand_supp.state((state, )) self.b_collapse_supp.state((state, )) self.b_open_orig.state((state, )) self.b_open_sauve.state((state, )) def update_pbar(self): """ Dislay the progress of the copy and deletion and put the GUI back in normal state once both processes are done. """ if not self.is_running_copie and not self.is_running_supp: notification_send(_("Sync is finished.")) self.toggle_state_gui() self.pbar_copie.configure(value=self.pbar_copie.cget("maximum")) self.pbar_supp.configure(value=self.pbar_supp.cget("maximum")) self.menu.entryconfigure(5, state="disabled") self.configure(cursor="") self.efface_tree() msg = "" if self.err_copie: msg += _( "There were errors during the copy, see %(file)s for more details.\n" ) % { 'file': self.log_copie } if self.err_supp: msg += _( "There were errors during the removal, see %(file)s for more details.\n" ) % { 'file': self.log_supp } if msg: showerror(_("Error"), msg, master=self) else: if not self.q_copie.empty(): self.pbar_copie.configure(value=self.q_copie.get()) if not self.q_supp.empty(): self.pbar_supp.configure(value=self.q_supp.get()) self.update() self.after(50, self.update_pbar) @staticmethod def get_list(tree): """Return the list of files/folders to copy/delete (depending on the tree).""" selected = [] def aux(item): tags = tree.item(item, "tags") if "checked" in tags and "whole" in tags: selected.append(item) elif "checked" in tags or "tristate" in tags: ch = tree.get_children(item) for c in ch: aux(c) ch = tree.get_children("") for c in ch: aux(c) return selected def synchronise(self): """ Display the list of files/folders that will be copied / deleted and launch the copy and deletion if the user validates the sync. """ # get files to delete and folder to delete if they are empty a_supp = self.get_list(self.tree_supp) # get files to copy a_copier = self.get_list(self.tree_copie) a_supp_avant_cp = [] for ch in self.pb_chemins: if ch in a_copier: a_supp_avant_cp.append( ch.replace(self.original, self.sauvegarde)) if a_supp or a_copier: Confirmation(self, a_copier, a_supp, a_supp_avant_cp, self.original, self.sauvegarde, self.show_size.get()) def copie_supp(self, a_copier, a_supp, a_supp_avant_cp): """Launch sync.""" self.toggle_state_gui() self.configure(cursor="watch") self.update() self.pbar_copie.state(("!disabled", )) self.pbar_supp.state(("!disabled", )) nbtot_copie = len(a_copier) + len(a_supp_avant_cp) self.pbar_copie.configure(maximum=nbtot_copie, value=0) nbtot_supp = len(a_supp) self.pbar_supp.configure(maximum=nbtot_supp, value=0) self.is_running_copie = True self.is_running_supp = True process_copie = Thread(target=self.copie, name="copie", daemon=True, args=(a_copier, a_supp_avant_cp)) process_supp = Thread(target=self.supp, daemon=True, name="suppression", args=(a_supp, )) process_copie.start() process_supp.start() self.pbar_copie.configure(value=0) self.pbar_supp.configure(value=0) self.update_pbar() def copie(self, a_copier, a_supp_avant_cp): """ Copie tous les fichiers/dossiers de a_copier de original vers sauvegarde en utilisant la commande système cp. Les erreurs rencontrées au cours du processus sont inscrites dans ~/.foldersync/copie.log """ self.err_copie = False orig = abspath(self.original) + "/" sauve = abspath(self.sauvegarde) + "/" chdir(orig) self.logger_copie.info( _("\n###### Copy: %(original)s -> %(backup)s\n") % { 'original': self.original, 'backup': self.sauvegarde }) n = len(a_supp_avant_cp) self.logger_copie.info(_("Removal before copy:")) for i, ch in zip(range(1, n + 1), a_supp_avant_cp): self.logger_copie.info(ch) p_copie = run(["rm", "-r", ch], stderr=PIPE) self.q_copie.put(i) err = p_copie.stderr.decode() if err: self.err_copie = True self.logger_copie.error(err.strip()) self.logger_copie.info(_("Copy:")) for i, ch in zip(range(n + 1, n + 1 + len(a_copier)), a_copier): ch_o = ch.replace(orig, "") self.logger_copie.info("%s -> %s" % (ch_o, sauve)) p_copie = run(["cp", "-ra", "--parents", ch_o, sauve], stderr=PIPE) self.q_copie.put(i) err = p_copie.stderr.decode() if err: self.err_copie = True self.logger_copie.error(err.strip()) self.is_running_copie = False def supp(self, a_supp): """ Supprime tous les fichiers/dossiers de a_supp de original vers sauvegarde en utilisant la commande système rm. Les erreurs rencontrées au cours du processus sont inscrites dans ~/.foldersync/suppression.log. """ self.err_supp = False self.logger_supp.info( _("\n###### Removal: %(original)s -> %(backup)s\n") % { 'original': self.original, 'backup': self.sauvegarde }) for i, ch in enumerate(a_supp): self.logger_supp.info(ch) p_supp = run(["rm", "-r", ch], stderr=PIPE) self.q_supp.put(i + 1) err = p_supp.stderr.decode() if err: self.logger_supp.error(err.strip()) self.err_supp = True self.is_running_supp = False def unlink(self): """Unlink pidfile.""" unlink(self.pidfile) def change_language(self): """Change app language.""" CONFIG.set("Defaults", "language", self.langue.get()) showinfo( _("Information"), _("The language setting will take effect after restarting the application" ))
class VadTest(Frame): """ A demo application class that provides simple GUI for testing voice activity detection on microphone or wav file input. """ def __init__(self, featurizer_path, input_device, wav_file, sample_rate): """ Initialize the VadTest object featurizer_path - path to the ELL featurizer to use input_device - id of the microphone to use wav_file - optional wav_file to use when you click play sample_rate - the sample rate to resample the incoming audio """ super().__init__() self.FEATURIZER_PATH_KEY = "featurizer_path" self.WAV_FILE_KEY = "wav_file" self.main_thread = get_ident() self.output_clear_time = 5000 self.channels = 1 self.init_ui() self.get_settings_file_name() self.load_settings() self.max_spectrogram_width = 120 self.spectrogram_image = None self.spectrogram_image_data = None self.show_spectrogram = True self.colormap_name = "inferno" self.min_value = 0.0 self.max_value = 1.0 self.update_minmax = True self.levels = [] self.signals = [] self.featurizer_path = None self.featurizer = None self.reading_input = False # Threads self.read_input_thread = None self.lock = Lock() self.main_thread = get_ident() self.message_queue = [] self.animation = None # featurizer if featurizer_path: self.featurizer_path = featurizer_path self.settings[self.FEATURIZER_PATH_KEY] = featurizer_path elif self.FEATURIZER_PATH_KEY in self.settings: self.featurizer_path = self.settings[self.FEATURIZER_PATH_KEY] self.sample_rate = sample_rate self.input_device = input_device self.wav_filename = None self.wav_file = None if wav_file: self.wav_filename = wav_file self.settings[self.WAV_FILE_KEY] = wav_file if self.wav_filename is None and self.WAV_FILE_KEY in self.settings: self.wav_filename = self.settings[self.WAV_FILE_KEY] self.wav_file_list = None self.speaker = None self.microphone = None self.save_settings() # in case we just changed it. if self.featurizer_path: self.load_featurizer_model(os.path.abspath(self.featurizer_path)) else: self.show_output("Please specify and load a feature model") self.update_ui() def init_ui(self): self.master.title("VAD Test") self.pack(side="top", fill=BOTH, expand=True) # VAD Controls section for controlling these VAD settings: controls_frame = LabelFrame(self, text="Controls", height=30) Label(controls_frame, text="tau_up:").grid(row=0, column=0) self.tau_up = Entry(controls_frame, width=15) self.tau_up.grid(row=1, column=0) Label(controls_frame, text="tau_down:").grid(row=0, column=1) self.tau_down = Entry(controls_frame, width=15) self.tau_down.grid(row=1, column=1) Label(controls_frame, text="threshold_up:").grid(row=0, column=2) self.threshold_up = Entry(controls_frame, width=15) self.threshold_up.grid(row=1, column=2) Label(controls_frame, text="threshold_down:").grid(row=0, column=3) self.threshold_down = Entry(controls_frame, width=15) self.threshold_down.grid(row=1, column=3) Label(controls_frame, text="large_input:").grid(row=0, column=4) self.large_input = Entry(controls_frame, width=15) self.large_input.grid(row=1, column=4) Label(controls_frame, text="gain_att:").grid(row=0, column=5) self.gain_att = Entry(controls_frame, width=15) self.gain_att.grid(row=1, column=5) Label(controls_frame, text="level_threshold:").grid(row=0, column=6) self.level_threshold = Entry(controls_frame, width=15) self.level_threshold.grid(row=1, column=6) controls_frame.pack(side=TOP) # Input section input_frame = LabelFrame(self, text="Input") input_frame.bind("-", self.on_minus_key) input_frame.bind("+", self.on_plus_key) input_frame.pack(fill=X) self.play_button = Button(input_frame, text="Play", command=self.on_play_button_click) self.play_button.pack(side=RIGHT, padx=4) self.rec_button = Button(input_frame, text="Rec", command=self.on_rec_button_click) self.rec_button.pack(side=RIGHT, padx=4) self.wav_filename_entry = Entry(input_frame, width=24) self.wav_filename_entry.pack(fill=X) self.wav_filename_entry.delete(0, END) # Feature section features_frame = LabelFrame(self, text="Features") features_frame.pack(fill=X) features_control_frame = Frame(features_frame) features_control_frame.pack(fill=X) load_features_button = Button(features_control_frame, text="Load", command=self.on_load_featurizer_model) load_features_button.pack(side=RIGHT) self.features_entry = Entry(features_control_frame, width=8) self.features_entry.pack(fill=X) self.features_entry.delete(0, END) viz_frame = Frame(features_frame) viz_frame.bind("%w", self.on_resized) viz_frame.pack(fill=X) self.features_figure = Figure(figsize=(5, 4), dpi=96) self.subplot = self.features_figure.add_subplot(211) self.subplot2 = self.features_figure.add_subplot(212) self.canvas = FigureCanvasTkAgg(self.features_figure, master=viz_frame) self.canvas.draw() self.canvas.show() self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=True) # Output section output_frame = LabelFrame(self, text="Output") output_frame.pack(fill=BOTH, expand=True) self.bind("<Configure>", self.on_resized) self.output_text = Text(output_frame) self.output_text.pack(fill=BOTH, padx=4, expand=True) def on_resized(self, event): window_size = event.width box = self.spectrogram_image.get_window_extent() scale = (box.x1 - box.x0) / self.max_spectrogram_width self.max_spectrogram_width = int((window_size * 0.8) / scale) self.setup_spectrogram_image() def load_featurizer_model(self, featurizer_path): """ load the given compiled ELL featurizer for use in processing subsequent audio input """ if featurizer_path: self.featurizer = featurizer.AudioTransform(featurizer_path, 40) self.setup_spectrogram_image() self.vad = vad.VoiceActivityDetector(self.sample_rate, self.featurizer.output_size) self.show_output("Feature input size: {}, output size: {}".format( self.featurizer.input_size, self.featurizer.output_size)) self.init_data() def setup_spectrogram_image(self): """ this need to be called if you load a new feature model, because the featurizer output size might have changed. """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) self.subplot.clear() self.spectrogram_image = self.subplot.imshow( self.spectrogram_image_data, vmin=self.min_value, vmax=self.max_value, origin="lower", animated=True, cmap=pyplot.get_cmap(self.colormap_name)) def accumulate_spectrogram_image(self, feature_data): """ accumulate the feature data into the spectrogram image """ image_data = self.spectrogram_image_data feature_data = np.reshape(feature_data, [-1, 1]) new_image = np.hstack( (image_data, feature_data))[:, -image_data.shape[1]:] image_data[:, :] = new_image def set_spectrogram_image(self): """ update the spectrogram image and the min/max values """ self.lock.acquire() # protect access to the shared state if self.update_minmax and self.show_spectrogram: min_value = np.min(self.spectrogram_image_data) max_value = np.max(self.spectrogram_image_data) if np.isfinite(min_value) and np.isfinite(max_value): self.min_value = min_value self.max_value = max_value eps = 0.1 if self.max_value - self.min_value < eps: self.max_value = self.min_value + eps self.spectrogram_image.set_clim(self.min_value, self.max_value) self.spectrogram_image.set_data(self.spectrogram_image_data) self.lock.release() def on_load_featurizer_model(self): """ called when user clicks the Load button for the feature model """ filename = self.features_entry.get() filename = filename.strip('"') self.featurizer_path = filename self.get_sample_rate() self.settings[self.FEATURIZER_PATH_KEY] = filename self.save_settings() self.stop() self.load_featurizer_model(filename) def set_entry(self, e, value): s = str(value) if e.get() != s: e.delete(0, END) e.insert(0, s) def get_entry(self, e): v = e.get() return float(v) def update_ui(self): self.set_entry(self.wav_filename_entry, self.wav_filename) self.set_entry(self.features_entry, self.featurizer_path) self.set_entry(self.tau_up, vad.DEFAULT_TAU_UP) self.set_entry(self.tau_down, vad.DEFAULT_TAU_DOWN) self.set_entry(self.threshold_up, vad.DEFAULT_THRESHOLD_UP) self.set_entry(self.threshold_down, vad.DEFAULT_THRESHOLD_DOWN) self.set_entry(self.large_input, vad.DEFAULT_LARGE_INPUT) self.set_entry(self.gain_att, vad.DEFAULT_GAIN_ATT) self.set_entry(self.level_threshold, vad.DEFAULT_LEVEL_THRESHOLD) def read_ui_settings(self): self.vad.configure(self.get_entry(self.tau_up), self.get_entry(self.tau_down), self.get_entry(self.threshold_up), self.get_entry(self.threshold_down), self.get_entry(self.large_input), self.get_entry(self.gain_att), self.get_entry(self.level_threshold)) def init_data(self): """ initialize the spectrogram_image_data based on the newly loaded model info """ if self.featurizer: dim = (self.featurizer.output_size, self.max_spectrogram_width) self.spectrogram_image_data = np.zeros(dim, dtype=float) if self.spectrogram_image is not None: self.spectrogram_image.set_data(self.spectrogram_image_data) def get_settings_file_name(self): """ this app stores the various UI field values in a settings file in your temp folder so you don't always have to specify the full command line options """ import tempfile temp = tempfile.gettempdir() self.settings_file_name = os.path.join(temp, "ELL", "Audio", "vad_test.json") def load_settings(self): """ load the previously saved settings from disk, if any """ self.settings = {} try: if os.path.isfile(self.settings_file_name): with open(self.settings_file_name, "r") as f: self.settings = json.load(f) except: self.show_output("error loading settings: {}".format( self.settings_file_name)) self.settings = {} def save_settings(self): """ save the current settings to disk """ settings_dir = os.path.dirname(self.settings_file_name) if not os.path.isdir(settings_dir): os.makedirs(settings_dir) with open(self.settings_file_name, "w") as f: f.write(json.dumps(self.settings)) def on_rec_button_click(self): """ called when user clicks the record button, same button is used to "stop" recording. """ if self.rec_button["text"] == "Rec": self.rec_button["text"] = "Stop" self.play_button["text"] = "Play" self.start_recording() else: self.rec_button["text"] = "Rec" self.on_stopped() def on_play_button_click(self): """ called when user clicks the record button, same button is used to "stop" playback """ if self.play_button["text"] == "Play": self.play_button["text"] = "Stop" self.rec_button["text"] = "Rec" self.on_play() else: self.play_button["text"] = "Play" self.on_stopped() def on_play(self): """ called when user clicks the Play button """ filename = self.wav_filename_entry.get() filename = filename.strip('"') self.wav_filename = filename self.settings[self.WAV_FILE_KEY] = filename self.save_settings() self.start_playing(filename) def on_stop(self): """ called when user clicks the Stop button """ self.reading_input = False if self.wav_file: self.wav_file.close() self.wav_file = None if self.read_input_thread: self.read_input_thread.join() self.read_input_thread = None self.stop() def on_stopped(self): """ called when we reach the end of the wav file playback """ self.play_button["text"] = "Play" self.on_stop() self.subplot2.clear() if (len(self.levels) > 0): levels = np.array(self.levels) levels /= np.max(levels) signals = np.array(self.signals) self.subplot2.plot(levels) self.subplot2.plot(signals) self.vad.reset() self.canvas.draw() self.canvas.show() self.levels = [] self.signals = [] def stop(self): """ called when user clicks the stop button, or we reach the end of a wav file input """ # close streams if self.animation: self.animation.event_source.stop() self.animation = None if self.microphone: self.microphone.close() if self.speaker: self.speaker.close() if self.wav_file: self.wav_file.close() self.wav_file = None self.reading_input = False def get_wav_list(self): if self.wav_filename and os.path.isfile(self.wav_filename): full_path = os.path.abspath(self.wav_filename) dir_name = os.path.dirname(full_path) if not self.wav_file_list: print("wav file name: {}".format(full_path)) print("looking for wav files in: {}".format(dir_name)) self.wav_file_list = [ x for x in os.listdir(dir_name) if os.path.splitext(x)[1] == ".wav" ] self.wav_file_list.sort() return self.wav_file_list def select_wav_file(self, filename): self.wav_filename = filename # show the file in the UI self.wav_filename_entry.delete(0, END) if self.wav_filename: self.wav_filename_entry.insert(0, self.wav_filename) # and automatically play the file. self.on_play() def on_minus_key(self, event): """ When user presses the plus button we reverse to the previous wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i - 1 >= 0: next_wav_file = self.wav_file_list[i - 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def on_plus_key(self, event): """ When user presses the plus button we advance to the next wav file in the current folder. This way you can easily step through all the training wav files """ if self.get_wav_list(): i = self.wav_file_list.index(os.path.basename(self.wav_filename)) if i + 1 < len(self.wav_file_list): next_wav_file = self.wav_file_list[i + 1] dir_name = os.path.dirname(self.wav_filename) self.select_wav_file(os.path.join(dir_name, next_wav_file)) def clear_output(self): """ remove some of the Output based a the timeout callback """ self.output_text.delete(1.0, 2.0) def process_output(self): """ show output that was queued by background thread """ self.lock.acquire() messages = self.message_queue self.message_queue = [] self.lock.release() for msg in messages: self.show_output(msg) def show_output(self, message): """ show output message, or queue it if we are on a background thread """ if self.main_thread != get_ident(): self.message_queue += [message] return for line in str(message).split('\n'): self.output_text.insert(END, "{}\n".format(line)) self.output_text.see("end") # scroll to end self.after(self.output_clear_time, self.clear_output) def start_playing(self, filename): """ Play a wav file, and classify the audio. Note we use a background thread to read the wav file and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the audio playback """ #if self.speaker is None: # self.speaker = speaker.Speaker() self.stop() self.read_ui_settings() self.reading_input = False self.wav_file = wav_reader.WavReader(self.sample_rate, self.channels) self.wav_file.open(filename, self.featurizer.input_size, self.speaker) self.setup_spectrogram_image() def update_func(frame_index): self.process_output() if not self.reading_input: self.after(1, self.on_stopped) self.set_spectrogram_image() return (self.spectrogram_image, ) if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) (30 fps is usually fine) self.animation = animation.FuncAnimation(self.features_figure, update_func, interval=33, blit=True) # start background thread to read and classify the audio. self.featurizer.open(self.wav_file) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def start_recording(self): """ Start recording audio from the microphone nd classify the audio. Note we use a background thread to process the audio and we setup a UI animation function to draw the sliding spectrogram image, this way the UI update doesn't interfere with the smoothness of the microphone readings """ if self.microphone is None: self.microphone = microphone.Microphone(False) self.stop() self.read_ui_settings() num_channels = 1 self.microphone.open(self.featurizer.input_size, self.sample_rate, num_channels, self.input_device) def update_func(frame_index): # this is an animation callback to update the UI every 33 milliseconds. self.process_output() self.set_spectrogram_image() if not self.reading_input: self.after(1, self.on_stopped) return (self.spectrogram_image, ) if self.animation: self.animation.event_source.stop() self.reading_input = True # Start animation timer for updating the UI (e.g. spectrogram image) (30 fps is usually fine) self.animation = animation.FuncAnimation(self.features_figure, update_func, interval=33, blit=True) # start background thread to read and classify the recorded audio. self.featurizer.open(self.microphone) self.read_input_thread = Thread(target=self.on_read_features, args=()) self.read_input_thread.daemon = True self.read_input_thread.start() def on_read_features(self): """ this is the background thread entry point. So we read the feature data in a loop """ try: while self.reading_input and self.featurizer: feature_data = self.featurizer.read() if feature_data is None: break # eof else: signal = self.vad.process(feature_data) self.levels += [self.vad.level] self.signals += [signal] self.lock.acquire() if self.show_spectrogram: self.accumulate_spectrogram_image(feature_data) self.lock.release() except: errorType, value, traceback = sys.exc_info() print("### Exception reading input: " + str(errorType) + ": " + str(value) + " " + str(traceback)) while traceback: print(traceback.tb_frame.f_code) traceback = traceback.tb_next self.reading_input = False
class Tampil(Tk): def __init__(self): super().__init__() self.dekPrev = myDek() self.dekNext = myDek() self.dekHistory = myDek() self.current = "" #Windows self.title("Stack Using Deque") self.geometry("400x160") #Label self.lblTitle = Label(self, text="Bejjo Parse Browser", foreground="blue") self.lblTitle.grid(row=0, column=2, pady=5, padx=2) self.lblBody = Label(self, text="Welcome") self.lblBody.grid(row=2, column=0, columnspan=4, pady=5, padx=2) #Button self.btnPrev = Button(self, text="<", width=5, state="disabled", command=self.prev) self.btnPrev.grid(row=1, column=0, pady=5, padx=2) self.btnNext = Button(self, text=">", width=5, state="disabled", command=self.next) self.btnNext.grid(row=1, column=1, pady=5, padx=2) self.btnSearch = Button(self, text="Search", width=8, command=self.search) self.btnSearch.grid(row=1, column=3, pady=5, padx=2) self.btnHistory = Button(self, text="History", width=12, command=self.history) self.btnHistory.grid(row=0, column=0, columnspan=2, pady=5, padx=2) self.btnParse = Button(self, text="Parse", width=8, command=self.parse) self.btnParse.grid(row=0, column=3, pady=5, padx=2) #Entry self.txtSearch = Entry(self, width=40) self.txtSearch.grid(row=1, column=2, pady=5, padx=2) def parse(self): if self.btnParse["text"] == "Parse": teks = "Welcome" if self.current != "": teks = "" url = Url(self.current) for x in url: teks += x + "\n" self.lblBody["text"] = teks self.btnParse["text"] = "Close" self.btnHistory["text"] = "History" else: self.btnParse["text"] = "Parse" self.switch_attr() def history(self): if self.btnHistory["text"] == "History": self.lblBody["text"] = self.dekHistory self.btnHistory["text"] = "Close" self.btnParse["text"] = "Parse" else: self.btnHistory["text"] = "History" self.switch_attr() def data(self): teks = "Welcome" if self.current != "": url = Url(self.current) teks = url.title() return teks def switch_attr(self): if len(self.dekPrev) == 0: self.btnPrev["state"] = "disabled" else: self.btnPrev["state"] = "normal" if len(self.dekNext) == 0: self.btnNext["state"] = "disabled" else: self.btnNext["state"] = "normal" self.txtSearch.delete(0, "end") self.txtSearch.insert(0, self.current) teks = self.data() self.lblBody["text"] = teks def prev(self): self.dekNext + self.current self.current = self.dekPrev.back() self.btnHistory["text"] = "History" self.btnParse["text"] = "Parse" self.switch_attr() def next(self): self.dekPrev + self.current self.current = self.dekNext.back() self.btnHistory["text"] = "History" self.btnParse["text"] = "Parse" self.switch_attr() def search(self): url = self.txtSearch.get().strip() if self.current == url and url: self.btnHistory["text"] = "History" self.btnParse["text"] = "Parse" self.switch_attr() elif "http" == url[:4] and len(url.replace( "http", "", 1).strip()) > 5 and "." in url: self.dekPrev + self.current self.current = url self.dekNext = myDek() self.dekHistory.history(self.current) self.btnHistory["text"] = "History" self.btnParse["text"] = "Parse" self.switch_attr() else: messagebox.showinfo("Warning", "Incorrect URL")