class OptionWindow(Toplevel): def __call__(self, options=[], display=True): self.options = options self.listbox.delete(0, END) for key, value in options: self.listbox.insert(END, key) if display: self.display() def __init__(self): Toplevel.__init__(self, master=root) self.options = None self.title('Matches') self.listbox = Listbox(master=self) self.listbox.pack(expand=True, fill=BOTH, side=TOP) self.listbox.focus_set() self.listbox.activate(0) self.listbox.selection_set(0) self.listbox.config(width=50) self.listbox.bind('<Key-h>', lambda event: self.listbox.event_generate('<Left>')) self.listbox.bind('<Key-l>', lambda event: self.listbox.event_generate('<Right>')) self.listbox.bind('<Key-k>', lambda event: self.listbox.event_generate('<Up>')) self.listbox.bind('<Key-j>', lambda event: self.listbox.event_generate('<Down>')) self.listbox.bind('<Escape>', lambda event: self.close()) self.protocol("WM_DELETE_WINDOW", self.close) self.transient(root) self.withdraw() def display(self): self.grab_set() self.deiconify() self.listbox.focus_set() # self.wait_window(self) def close(self): # When calling destroy or withdraw without # self.deoiconify it doesnt give back # the focus to the parent window. self.deiconify() self.grab_release() self.withdraw()
class RSVisCanvasFrame(Frame): # method -------------------------------------------------------------- # ----------------------------------------------------------------------- def __init__( self, parent, images, data, **kwargs ): super(RSVisCanvasFrame, self).__init__(parent) self.columnconfigure(2, weight=1) self.rowconfigure(0, weight=1) self.scrollbar = Scrollbar(self, orient="vertical", width=16) self.listbox = Listbox(self, yscrollcommand=self.scrollbar.set, width=37, activestyle=UNDERLINE) self.scrollbar.config(command=self.listbox.yview) self.scrollbar.grid(row=0, column=0, sticky=N+S) self.listbox.grid(row=0, column=1, sticky=N+S) for count, item in enumerate(images): self.listbox.insert(END, pathlib.Path(item[0].path).stem) self.listbox.bind("<<ListboxSelect>>", self.listbox_event) self._canvas = rsviscv.RSVisCanvas(self, images, data, **kwargs) self._canvas.set_container() self._canvas.grid(row=0, column=2, sticky=N+S+E+W) # parent.bind("<a>", self.key_a) # parent.bind("<d>", self.key_d) # method -------------------------------------------------------------- # ----------------------------------------------------------------------- def listbox_event(self,event): try: self._canvas._idx_list(index=self.listbox.curselection()[0]) self._canvas.set_container() except IndexError: pass # method -------------------------------------------------------------- # ----------------------------------------------------------------------- def update_listbox(self, event): self.listbox.activate(self._canvas.get_idx_list()) # method -------------------------------------------------------------- # ----------------------------------------------------------------------- def key_a(self, event, **kwargs): """Show the previous image in given image list (see listbox).""" self.update_listbox(event) # method -------------------------------------------------------------- # ----------------------------------------------------------------------- def key_d(self, event, **kwargs): """Show the next image in given image list (see listbox).""" self.update_listbox(event)
class List: def __init__(self, root, array=[]): self.__root = root self.__frame = Frame(self.__root) self.__scrollBar = Scrollbar(self.__frame) self.__list = Listbox(self.__frame, font=12, selectmode=SINGLE, width=10, yscrollcommand=self.__scrollBar.set) self.__list.config(width=40) self.__list.pack(side=LEFT, anchor=W, fill=BOTH, expand=True) self.__lessons = [] self.setList(array) self.__scrollBar.config(command=self.__list.yview) self.__scrollBar.pack(side=RIGHT, fill=Y) self.__frame.pack(side=LEFT, anchor=W, fill=BOTH, expand=True) def getObjectList(self): return self.__list def setList(self, array=[]): self.__deleteAll() self.__lessons = [x for x in array] for item in array: self.__list.insert(END, str(item)) def getSelectedElement(self, e=None): if self.__list.curselection() is (): return None else: return self.__lessons[self.__list.curselection()[0]] def setSelectedItem(self, i): self.__list.activate(i) def __deleteAll(self): self.__list.delete(0, END)
class SerialConfigFrame( Frame ): # pylint: disable=too-many-ancestors, too-many-instance-attributes """ Serial port configuration frame class. """ def __init__(self, container, *args, **kwargs): """ Constructor. :param tkinter.Frame container: reference to container frame :param args: optional args to pass to Frame parent class :param kwargs: optional kwargs for value ranges, or to pass to Frame parent class """ self._bpsrate_rng = kwargs.pop("bpsrates", BPSRATE_RNG) self._databits_rng = kwargs.pop("databits", DATABITS_RNG) self._stopbits_rng = kwargs.pop("stopbits", STOPBITS_RNG) self._parity_rng = kwargs.pop("parities", PARITY_RNG) self._timeout_rng = kwargs.pop("timeouts", TIMEOUT_RNG) self._preselect = kwargs.pop("preselect", ()) self._readonlybg = kwargs.pop("readonlybackground", BGCOL) Frame.__init__(self, container, *args, **kwargs) self._show_advanced = False self._noports = True self._ports = () self._port = StringVar() self._port_desc = StringVar() self._bpsrate = IntVar() self._databits = IntVar() self._stopbits = DoubleVar() self._parity = StringVar() self._rtscts = IntVar() self._xonxoff = IntVar() self._timeout = StringVar() self._body() self._do_layout() self._get_ports() self._attach_events() self.reset() def _body(self): """ Set up widgets. """ self._frm_basic = Frame(self) self._lbl_port = Label(self._frm_basic, text="Port") self._lbx_port = Listbox( self._frm_basic, border=2, relief="sunken", bg=self._readonlybg, width=28, height=5, justify=LEFT, exportselection=False, ) self._scr_portv = Scrollbar(self._frm_basic, orient=VERTICAL) self._scr_porth = Scrollbar(self._frm_basic, orient=HORIZONTAL) self._lbx_port.config(yscrollcommand=self._scr_portv.set) self._lbx_port.config(xscrollcommand=self._scr_porth.set) self._scr_portv.config(command=self._lbx_port.yview) self._scr_porth.config(command=self._lbx_port.xview) self._lbl_bpsrate = Label(self._frm_basic, text="Rate bps") self._spn_bpsrate = Spinbox( self._frm_basic, values=self._bpsrate_rng, width=8, state=READONLY, readonlybackground=self._readonlybg, wrap=True, textvariable=self._bpsrate, ) self._btn_toggle = Button( self._frm_basic, text=ADVOFF, width=3, command=self._on_toggle_advanced ) self._frm_advanced = Frame(self) self._lbl_databits = Label(self._frm_advanced, text="Data Bits") self._spn_databits = Spinbox( self._frm_advanced, values=self._databits_rng, width=3, state=READONLY, readonlybackground=self._readonlybg, wrap=True, textvariable=self._databits, ) self._lbl_stopbits = Label(self._frm_advanced, text="Stop Bits") self._spn_stopbits = Spinbox( self._frm_advanced, values=self._stopbits_rng, width=3, state=READONLY, readonlybackground=self._readonlybg, wrap=True, textvariable=self._stopbits, ) self._lbl_parity = Label(self._frm_advanced, text="Parity") self._spn_parity = Spinbox( self._frm_advanced, values=self._parity_rng, width=6, state=READONLY, readonlybackground=self._readonlybg, wrap=True, textvariable=self._parity, ) self._chk_rts = Checkbutton( self._frm_advanced, text="RTS/CTS", variable=self._rtscts ) self._chk_xon = Checkbutton( self._frm_advanced, text="Xon/Xoff", variable=self._xonxoff ) self._lbl_timeout = Label(self._frm_advanced, text="Timeout (s)") self._spn_timeout = Spinbox( self._frm_advanced, values=self._timeout_rng, width=4, state=READONLY, readonlybackground=self._readonlybg, wrap=True, textvariable=self._timeout, ) def _do_layout(self): """ Layout widgets. """ self._frm_basic.grid(column=0, row=0, columnspan=4, sticky=(W, E)) self._lbl_port.grid(column=0, row=0, sticky=(W)) self._lbx_port.grid(column=1, row=0, sticky=(W, E), padx=3, pady=3) self._scr_portv.grid(column=2, row=0, sticky=(N, S)) self._scr_porth.grid(column=1, row=1, sticky=(E, W)) self._lbl_bpsrate.grid(column=0, row=2, sticky=(W)) self._spn_bpsrate.grid(column=1, row=2, sticky=(W), padx=3, pady=3) self._btn_toggle.grid(column=2, row=2, sticky=(E)) self._frm_advanced.grid_forget() self._lbl_databits.grid(column=0, row=0, sticky=(W)) self._spn_databits.grid(column=1, row=0, sticky=(W), padx=3, pady=3) self._lbl_stopbits.grid(column=2, row=0, sticky=(W)) self._spn_stopbits.grid(column=3, row=0, sticky=(W), padx=3, pady=3) self._lbl_parity.grid(column=0, row=1, sticky=(W)) self._spn_parity.grid(column=1, row=1, sticky=(W), padx=3, pady=3) self._chk_rts.grid(column=2, row=1, sticky=(W)) self._chk_xon.grid(column=3, row=1, sticky=(W), padx=3, pady=3) self._lbl_timeout.grid(column=0, row=2, sticky=(W)) self._spn_timeout.grid(column=1, row=2, sticky=(W), padx=3, pady=3) def _attach_events(self): """ Bind events to widgets. """ self._lbx_port.bind("<<ListboxSelect>>", self._on_select_port) def _get_ports(self): """ Populate list of available serial ports using pyserial comports tool. Attempt to automatically select a serial device matching a list of 'preselect' devices (only works on platforms which parse UART device desc or HWID e.g. Posix). """ self._ports = sorted(comports()) init_idx = 0 port = "" desc = "" if len(self._ports) > 0: for idx, (port, desc, _) in enumerate(self._ports, 1): self._lbx_port.insert(idx, port + ": " + desc) for dev in self._preselect: if dev in desc: init_idx = idx break self._noports = False else: self._noports = True self.enable_controls(True) self._lbx_port.activate(init_idx) self._port.set(port) self._port_desc.set(desc) def _on_select_port(self, *args, **kwargs): # pylint: disable=unused-argument """ Get selected port from listbox and set global variable. """ idx = self._lbx_port.curselection() if idx == "": idx = 0 port_orig = self._lbx_port.get(idx) port = port_orig[0: port_orig.find(":")] desc = port_orig[port_orig.find(":") + 1:] if desc == "": desc = "device" self._port.set(port) self._port_desc.set(desc) def _on_toggle_advanced(self): """ Toggle advanced serial port settings panel on or off. """ self._show_advanced = not self._show_advanced if self._show_advanced: self._frm_advanced.grid(column=0, row=1, columnspan=3, sticky=(W, E)) self._btn_toggle.config(text=ADVON) else: self._frm_advanced.grid_forget() self._btn_toggle.config(text=ADVOFF) def enable_controls(self, disabled: bool=False): """ Enable or disable controls (e.g. to prevent changes when serial device is connected). :param bool disabled: disable controls flag """ for widget in ( self._lbl_port, self._lbl_bpsrate, self._lbl_databits, self._lbl_stopbits, self._lbl_parity, self._lbl_timeout, self._chk_rts, self._chk_xon, self._lbx_port, ): widget.configure(state=(DISABLED if disabled else NORMAL)) for widget in ( self._spn_bpsrate, self._spn_databits, self._spn_stopbits, self._spn_parity, self._spn_timeout, ): widget.configure(state=(DISABLED if disabled else READONLY)) def reset(self): """ Reset settings to defaults (first value in range). """ self._bpsrate.set(self._bpsrate_rng[0]) self._databits.set(self._databits_rng[0]) self._stopbits.set(self._stopbits_rng[0]) self._parity.set(self._parity_rng[0]) self._rtscts.set(False) self._xonxoff.set(False) self._timeout.set(self._timeout_rng[0]) @property def noports(self) -> bool: """ Getter for noports flag, which indicates if there are no serial ports available. :return: noports flag (True/False) :rtype: bool """ return self._noports @property def port(self) -> str: """ Getter for port. :return: selected port :rtype: str """ return self._port.get() @property def port_desc(self) -> str: """ Getter for port description. :return: selected port description :rtype: str """ return self._port_desc.get() @property def bpsrate(self) -> int: """ Getter for bps rate (commonly but incorrectly referred to as baud rate). :return: selected baudrate :rtype: int """ return self._bpsrate.get() @property def databits(self) -> int: """ Getter for databits. :return: selected databits :rtype: int """ return self._databits.get() @property def stopbits(self) -> float: """ Getter for stopbits. :return: selected stopbits :rtype: float """ return self._stopbits.get() @property def parity(self) -> str: """ Getter for parity. :return: selected parity :rtype: str """ return PARITIES[self._parity.get()] @property def rtscts(self) -> bool: """ Getter for rts/cts. :return: selected rts/cts :rtype: bool """ return self._rtscts.get() @property def xonxoff(self) -> bool: """ Getter for xon/xoff. :return: selected xon/xoff :rtype: bool """ return self._xonxoff.get() @property def timeout(self) -> float: """ Getter for timeout. :return: selected timeout :rtype: float (or None) """ if self._timeout.get() == "None": return None return float(self._timeout.get())
class AutocompleteEntry(Entry): def __init__(self, *args, **kwargs): Entry.__init__(self, width=100, *args, **kwargs) self.focus_set() self.pack() self.var = self["textvariable"] if self.var == '': self.var = self["textvariable"] = StringVar() self.var.trace('w', self.changed) self.bind("<Right>", self.selection) self.bind("<Up>", self.up) self.bind("<Down>", self.down) self.bind("<Return>", self.enter) self.lb_up = False self.lb = None def enter(self, event): print(event) def changed(self, name, index, mode): if self.var.get() == '': if self.lb: self.lb.destroy() self.lb_up = False else: words = self.comparison() if words: if not self.lb_up: self.lb = Listbox(master=root, width=100) self.lb.bind("<Double-Button-1>", self.selection) self.lb.bind("<Right>", self.selection) self.lb.place(x=self.winfo_x(), y=self.winfo_y()+self.winfo_height()) self.lb_up = True self.lb.delete(0, END) for w in words: self.lb.insert(END,w) else: if self.lb_up: self.lb.destroy() self.lb_up = False def selection(self, _): if self.lb_up: self.var.set(self.lb.get(ACTIVE)) self.lb.destroy() self.lb_up = False self.icursor(END) def up(self, _): if self.lb_up: if self.lb.curselection() == (): index = '0' else: index = self.lb.curselection()[0] if index != '0': self.lb.selection_clear(first=index) index = str(int(index)-1) self.lb.selection_set(first=index) self.lb.activate(index) def down(self, _): if self.lb_up: if self.lb.curselection() == (): index = '0' else: index = self.lb.curselection()[0] if index != END: self.lb.selection_clear(first=index) index = str(int(index)+1) self.lb.selection_set(first=index) self.lb.activate(index) def comparison(self): q = self.var.get() q = str(q.decode('utf8')) for hit in searcher.search(qp.parse(q), limit=50): if hit['author']: yield '%s. "%s"' % (hit['author'], hit['title']) else: yield hit['title']
class TMaterialsWindow(Frame): """ Class represents auxiliary window containg materials database. """ def __init__(self, master, TApp, TShapesWindow): """ Call the parent class constructor and initialise object variables. """ super().__init__() self.TApp = TApp self.TShapesWindow = TShapesWindow self.round_digits = 2 self.init_widgets () # Bind this class methods to manin menubar self.TApp.file_menu.entryconfig("Open materials", command = self.open_file) self.TApp.file_menu.entryconfig("Save materials", command = self.save_file) def init_widgets(self): """ Initialise widgets. """ # Set weights for columns and rows for i in range(6): self.grid_columnconfigure (i, weight = 1, uniform = "material_cols", minsize = 50) self.grid_rowconfigure (1, weight = 1) # self.grid_rowconfigure (2, weight = 1) # Create and arrange widgets (using grid) self.open_file_button = Button(self, text = "Open", command = self.open_file) self.open_file_button.grid(row = 0, column = 0, sticky = NSEW, columnspan = 3, padx = 20, pady = 5) self.save_file_button = Button(self, text = "Save", command = self.save_file) self.save_file_button.grid(row = 0, column = 3, sticky = NSEW, columnspan = 3, padx = 20, pady = 5) # ListBox self.materials_list = Listbox(self, exportselection = False) self.materials_list.grid(row = 1, column = 0, columnspan = 6, sticky = NSEW, padx = (5, 25), pady = 5) self.materials_list.bind("<<ListboxSelect>>", self.material_list_selected_item) # ListBox's y-scrollbar self.materials_list_scrollbar = Scrollbar(self, orient = VERTICAL, command = self.materials_list.yview) self.materials_list_scrollbar.grid(row = 1, column = 5, sticky = NS + E, padx = (0, 5), pady = 5) self.materials_list.config(yscrollcommand = self.materials_list_scrollbar.set) self.name_label = Label(self, text = "Name", anchor = W) self.name_label.grid(row = 2, column = 0, columnspan = 1, sticky = EW) self.name_entry = Entry(self, text = "") self.name_entry.grid(row = 2, column = 1, columnspan = 5, sticky = NSEW, padx = 5, pady = 2) self.epsilon_r_label = Label(self, text = "\u03b5r", anchor = W) self.epsilon_r_label.grid(row = 3, column = 0, columnspan = 1, sticky = EW) self.epsilon_r_entry = Entry(self, text = "") self.epsilon_r_entry.grid(row = 3, column = 1, columnspan = 5, sticky = NSEW, padx = 5, pady = 2) self.sigma_label = Label(self, text = "\u03c3", anchor = W) self.sigma_label.grid(row = 4, column = 0, columnspan = 1, sticky = EW) self.sigma_entry = Entry(self, text = "") self.sigma_entry.grid(row = 4, column = 1, columnspan = 5, sticky = NSEW, padx = 5, pady = 2) self.mu_r_label = Label(self, text = "\u03bcr", anchor = W) self.mu_r_label.grid(row = 5, column = 0, columnspan = 1, sticky = EW) self.mu_r_entry = Entry(self, text = "") self.mu_r_entry.grid(row = 5, column = 1, columnspan = 5, sticky = NSEW, padx = 5, pady = 2) self.sigma_mag_label = Label(self, text = "\u03c3*", anchor = W) self.sigma_mag_label.grid(row = 6, column = 0, columnspan = 1, sticky = EW) self.sigma_mag_entry = Entry(self, text = "") self.sigma_mag_entry.grid(row = 6, column = 1, columnspan = 5, sticky = NSEW, padx = 5, pady = 2) self.store_changed_params_button = Button(self, text = "Change", command = self.store_material_parameters) self.store_changed_params_button.grid(row = 7, column = 0, columnspan = 2, sticky = NSEW, padx = 5, pady = 5) self.delete_material_button = Button(self, text = "Delete", command = self.delete_material) self.delete_material_button.grid(row = 7, column = 2, columnspan = 2, sticky = NSEW, padx = 5, pady = 5) self.add_material_button = Button(self, text = "Add", command = self.add_material) self.add_material_button.grid(row = 7, column = 4, columnspan = 2, sticky = NSEW, padx = 5, pady = 5) def open_file(self): """ Read an ASCII file containing materials data. """ filename = filedialog.askopenfilename(initialdir = '.', title = "Select file", \ filetypes = [("All files", "*.*")]) try: # Check, if given name is empty string if(filename == ""): raise Exception("No filename given!") # Check, if given file is binary if(self.is_binary(filename)): raise Exception("Designated file is binary!") else: file = open(filename, "rt") except Exception as message: messagebox.showerror("File error", message) return try: self.TApp.material = [] except Exception as message: messagebox.showerror("Materials list error", message) return # Loop reading and parsing all lines one by one line_num = 1 try: for line in file: if(line[0] != "#"): # Omit lines that don't begin with hash sign (comments) continue else: tokens = line.split() if(len(tokens) == 6): epsilon_r = float(tokens[1]) sigma = float(tokens[2]) mu_r = float(tokens[3]) sigma_mag = float(tokens[4]) name = str(tokens[5]) self.TApp.materials.append(TMaterial(epsilon_r = epsilon_r, \ sigma = sigma, \ mu_r = mu_r, \ sigma_mag = sigma_mag,\ name = name)) else: raise Exception("Invalid number of inputs in line {}!".format(line_num)) line_num += 1 except Exception as message: messagebox.showerror("Invalid input format", message) self.TApp.materials = [] self.materials_list.delete(0, self.materials_list.size () ) return finally: file.close() # Check whether any materials were read if(len(self.TApp.materials) == 0): messagebox.showwarning("Empty file", "File does not contain any materials' information.") # Repleace material's list self.update_list(self.TApp.materials) def save_file(self): """ Save current materials database to an ASCII file. """ filename = filedialog.asksaveasfilename(initialdir = '.', title = "Select file", \ filetypes = [("All files", "*.*")]) try: # Check, if given name is empty string if(filename == ""): raise Exception("No filename given!") else: file = open(filename, "wt") except Exception as message: messagebox.showerror("File error", message) return try: if(len(self.TApp.materials) == 0): raise Exception ("Materials' list is empty!") else: for single_material in self.TApp.materials: file.write("# " + str(single_material.epsilon_r) + " " + \ str(single_material.sigma) + \ " " + str (single_material.mu_r) + " " + \ str(single_material.sigma_mag) + " " + \ str(single_material.name) + "\n") file.close() except Exception as message: messagebox.showerror("Error while writing file", message) return finally: file.close() def update_list(self, materials): """ Update shapes list. :param materials: new materials database. :type materials: list """ self.materials_list.delete(0, self.materials_list.size()) i = 0 info_string = "" materials_names_str = ["pec", "free_space"] for single_material in materials: info_string += str(single_material.name) + ": " + str(single_material.epsilon_r)+ " " + str(single_material.sigma) + " " + \ str(single_material.mu_r) + " " + str(single_material.sigma_mag) materials_names_str.append(str(single_material.name)) try: self.materials_list.insert (i, info_string) except Exception as message: messagebox.showerror ("List error", message) self.materials_list.delete (0, self.materials_list.size () ) return None i += 1 info_string = "" # Update shapes window combobox self.TShapesWindow.material_box.config(values = materials_names_str) def get_selected_item_num(self): """ Retrieve a index of a selected material on the list. :rtype: integer """ try: selection = self.materials_list.curselection() return selection[0] # except Exception as message: except: #messagebox.showerror("No material selected!", message) return -1 def material_list_selected_item(self, event): """ Display selected material parameters in the text fields. :param event: listbox LMB click event. :type event: tkinter.Event """ item_num = self.get_selected_item_num() if(item_num < 0): return None else: try: material = self.TApp.materials[item_num] except Exception as message: messagebox.showerror("Materials' list error", message) return None self.name_entry.delete(0, END) self.name_entry.insert(0, material.name) self.epsilon_r_entry.delete(0, END) self.epsilon_r_entry.insert(0, material.epsilon_r) self.sigma_entry.delete(0, END) self.sigma_entry.insert(0, material.sigma) self.mu_r_entry.delete(0, END) self.mu_r_entry.insert(0, material.mu_r) self.sigma_mag_entry.delete(0, END) self.sigma_mag_entry.insert(0, material.sigma_mag) self.materials_list.activate(item_num) def store_material_parameters(self): """ Update material parameters from values entered in the text fields. """ item_num = self.get_selected_item_num() if(item_num < 0): return None else: try: self.TApp.materials[item_num].name = str(self.name_entry.get()) self.TApp.materials[item_num].epsilon_r = float(self.epsilon_r_entry.get()) self.TApp.materials[item_num].sigma = float(self.sigma_entry.get()) self.TApp.materials[item_num].mu_r = float(self.mu_r_entry.get()) self.TApp.materials[item_num].sigma_mag = float(self.sigma_mag_entry.get()) self.update_list(self.TApp.materials) self.materials_list.select_set(item_num) except Exception as message: messagebox.showerror("Materials' list error", message) def delete_material(self): """ Delete a material from the database. """ item_num = self.get_selected_item_num() if (item_num < 0): return None else: try: del self.TApp.materials[item_num] self.update_list(self.TApp.materials) if(item_num == 0): self.materials_list.select_set(0) else: self.materials_list.select_set(item_num - 1) except Exception as message: messagebox.showerror("Materials' list error", message) def add_material(self): """ Add a material to the database. """ try: if(str(self.name_entry.get()) == ""): raise Exception("Material can't have name that's an empty string!") self.TApp.materials.append(TMaterial(epsilon_r = float(self.epsilon_r_entry.get()), \ sigma = float(self.sigma_entry.get()),\ mu_r = float(self.mu_r_entry.get()), \ sigma_mag = float(self.sigma_mag_entry.get()), \ name = str(self.name_entry.get()))) except Exception as message: messagebox.showerror("Error while creating material", message) self.update_list(self.TApp.materials) self.materials_list.select_set(END) def is_binary(self, filename): """ Check whether given file is binary (that is non-text). """ try: with open(filename, 'tr') as check_file: # try open file in text mode check_file.read() return False except: # if it fails then file is non-text (binary) return True finally: check_file.close()
class Editor: # Info Defaults LOG_FILE = 'editor.log' devices = [] vidPK = [] vidPATH = [] vidNAME = [] uuid = [] uuidFK = [] def __init__(self, master, soundGenerator, rfidScanner): self.environment = Environment() self.soundProvider = SoundProvider(soundGenerator) self.configureScannerProvider(rfidScanner) self.load() frame = Frame(master) frame.pack() self.activeCardNumber = StringVar() self.usbSpin = StringVar() self.usbSpin.set(self.environment.Usb) Label(frame, text='RFID Card').grid(row=0, column=0) Label(frame, text='Video').grid(row=0, column=2) self.ee = Entry(frame, textvariable=self.activeCardNumber, state=DISABLED, disabledforeground='black') self.ee.grid(row=1, column=0) self.r = Button(frame, text='Read Card', command=self.startCardProcess) self.r.grid(row=1, column=1) self.box = Listbox(frame) for entry in self.vidNAME: self.box.insert(END, entry) self.box.bind("<<ListboxSelect>>", self.newselection) self.box.grid(row=1, rowspan=5, column=2, columnspan=2) self.scroll = Scrollbar(self.box, orient=VERTICAL) self.box.config(yscrollcommand=self.scroll.set) self.scroll.config(command=self.box.yview) Button(frame, text='Assign Kill Code', command=self.createKiller).grid(row=2, column=0) Label(frame, text='Source USB').grid(row=4, column=0) self.spin = Spinbox(frame, values=self.devices) self.spin.delete(0, END) self.spin.insert(0, self.environment.Usb) self.spin.grid(row=5, column=0) self.status = Button(frame, text='Update Device Repository', command=self.updateDevice, disabledforeground='blue') self.status.grid(row=6, column=0) Button(frame, text='Save', command=self.save).grid(row=6, column=2) Button(frame, text='Quit', command=self.closeWithSavePrompt).grid( row=6, column=3) def configureScannerProvider(self, rfidScanner): provider = RFIDScannerProvider(rfidScanner) self.RFIDScannerProvider = provider.PN532( int(self.environment.CHIP_SELECT_PIN), int(self.environment.MASTER_OUTPUT_SLAVE_INPUT_PIN), int(self.environment.MASTER_INPUT_SLAVE_OUTPUT_PIN), int(self.environment.SERIAL_CLOCK_PIN)) def closeWithSavePrompt(self): ans = messagebox.askquestion( 'Save And Quit', 'Would you like to save your changes?') if ans == 'yes': self.save() sys.exit(0) def startCardProcess(self): # Disable button to prevent event stacking self.r.config(state=DISABLED) self.processCard() self.r.config(state=NORMAL) def processCard(self): # Scans RFID cards and sets them to text box try: self.processCardUnchecked() except Exception as e: self.displayScanError(e) def processCardUnchecked(self): cardScan = CardScanWrapper(self.soundS, self.RFIDScannerProvider) cardScan.runScan() self.processResult(cardScan.getFormattedResult()) def processResult(self, scanResult): if scanResult == None: return self.activeCardNumber.set(scanResult) # Populate text box self.deselectActiveListboxItems() self.linkCardWithListbox(scanResult) def deselectActiveListboxItems(self): # De-select any active items in listbox self.box.selection_clear(0, END) def linkCardWithListbox(self, scanResult): index = self.verifyCard(scanResult) if str(self.uuidFK[index]) == self.environment.KillCommand: messagebox.showinfo( 'Kill Card', 'This card is currently assigned to kill the application.') return self.highlightItemInListbox(index) def highlightItemInListbox(self, index): try: i = self.vidPK.index(self.uuidFK[index]) except: messagebox.showinfo('Card Unassigned', 'Card is not currently assigned to a video') else: self.box.see(i) self.box.selection_clear(0, END) self.box.selection_set(i) self.box.activate(i) def verifyCard(self, uidt): try: uuidIndex = self.uuid.index(uidt) except: uuidIndex = self.addNewCard(uidt) return uuidIndex def addNewCard(self, uidt): self.uuid.append(uidt) self.uuidFK.append(-1) newIndex = len(self.uuid) - 1 return newIndex def displayScanError(self, e): messagebox.showerror('Error Occurred', 'Error: ' + str(e)) logging.error('Scan Failed: ' + str(e)) def save(self): toSaveList = self.makePairedList( self.vidPK, self.vidNAME, self.vidPATH) self.safeSaveToFile(self.environment.VideoList, toSaveList) toSaveList = self.makePairedList(self.uuid, self.uuidFK) self.safeSaveToFile(self.environment.UuidTable, toSaveList) def makePairedList(self, *itemLists): stop = len(itemLists) subList = [] listAll = [] for listIndex in range(len(itemLists[0])): del subList[:] for indice in range(stop): subList.append(itemLists[indice][listIndex]) listAll.append(list(subList)) return listAll def safeSaveToFile(self, fileName, pairList): try: self.writePairedListToTempFile(fileName, pairList) except Exception as e: logging.error('Failed to create video list: ' + str(e)) else: self.replaceOriginalFileWithItsTemp(fileName) def replaceOriginalFileWithItsTemp(self, fileName): try: if os.path.isfile(fileName): os.remove(fileName) os.rename(fileName + '.temp', fileName) except Exception as e: logging.error('Failed to replace old video list: ' + str(e)) def writePairedListToTempFile(self, fileName, pairedList): f = open(fileName + '.temp', 'w') self.writePairedListGivenFile(f, pairedList) f.close() def writePairedListGivenFile(self, f, pairedList): i = 0 while(i < len(pairedList) - 1): self.writeSingleLineOfPairedListToOpenFile(f, pairedList, i) f.write('\n') i = i+1 self.writeSingleLineOfPairedListToOpenFile(f, pairedList, i) def writeSingleLineOfPairedListToOpenFile(self, f, pairedList, itemIndex): fLine = "" for item in range(len(pairedList[itemIndex])): fLine = fLine + str(pairedList[itemIndex][item]) + ',' f.write(fLine[:-1]) def updateDevice(self): scan = self.safeScan() if scan != None: self.safeProcessScan(scan) self.status.config(text='Update Device Repository', state=NORMAL) self.status.update_idletasks() def safeScan(self): scan = None try: scan = self.runScannerWithNotification() except Exception as e: self.showScanErrorMessage(e) return scan def runScannerWithNotification(self): self.status.config(text='Scanning...', state=DISABLED) self.status.update_idletasks() scan = ScriptedFileSearch(subprocess) scan.scan("scanner.sh") return scan def showScanErrorMessage(self, e): messagebox.showerror('Scan Failed', 'Scan error: ' + str(e)) logging.error(str(e)) def safeProcessScan(self, scan): try: self.processScan(scan) except Exception as e: self.showErrorMessage(e) def showErrorMessage(self, e): messagebox.showerror('Error', 'Error: ' + str(e)) logging.error(str(e)) def refreshListBox(self): self.box.delete(0, END) for entry in self.vidNAME: self.box.insert(END, entry) def processScan(self, scan): # Check if a scan turned up any results if self.scanIsEmpty(scan): self.showAbortScanMessage() return self.verifyUSB() self.processScanFiles(scan) self.refreshListBox() def showAbortScanMessage(self): messagebox.showwarning( 'No Files Found', 'A scan failed to find any files.') logging.warning('Empty Scan occurred when attempting a merge') def scanIsEmpty(self, scan): return len(scan.NAME) == 0 def verifyUSB(self): if self.environment.Usb != self.spin.get(): self.Usb = self.spin.get() self.environment.update() def processScanFiles(self, scan): i = 0 j = 0 newPK = [] newName = [] newPath = [] self.status.config(text='Reading Files...') self.status.update_idletasks() # Iterate through list while i < len(scan.NAME): # Verifiy File try: if scan.PATH[i].find(self.environment.Usb) >= 0: # File resides on repository - update FK try: # Locate matching entry fkk = self.vidNAME.index(scan.NAME[i]) except Exception as e: # No matching entry logging.info('New file found in repository: ' + str(e)) pass else: # Update FK on uuid table for uu in self.uuidFK: if uu == self.vidPK[fkk]: uu = scan.PK[i] # Store entry in new Tables newPK.append(scan.PK[i]) newName.append(scan.NAME[i]) newPath.append(scan.PATH[i]) else: # Video resides on updating device - check if file already copied found = False while j < len(scan.NAME): if str(scan.NAME[i]) == str(scan.NAME[j]) and scan.PATH[j].find(self.environment.Usb) >= 0: found = True break j = j + 1 if not found: # Copy file and append try: # Get device name device = scan.PATH[i].replace('/media/pi/', '') device = device[0:device.find('/')] # Copy self.status.config( text='Copying ' + scan.NAME[i] + '...') self.status.update_idletasks() shutil.copyfile( scan.PATH[i], scan.PATH[i].replace(device, self.environment.Usb)) except Exception as e: logging.error('Failed to copy' + scan.NAME[i] + ': ' + str(e)) else: # Add to new array newPK.append(scan.PK[i]) newName.append(scan.NAME[i]) newPath.append( scan.PATH[i].replace(device, self.environment.Usb)) except Exception as e: logging.error(str(e)) i = i+1 del self.vidNAME[:] del self.vidPATH[:] del self.vidPK[:] self.vidNAME = newName self.vidPATH = newPath self.vidPK = newPK def newselection(self, event): # Fires when a new item is selected in the listbox selection = event.widget.curselection() try: txt = self.ee.get() if txt == '': return i = self.uuid.index(txt) self.uuidFK[i] = self.vidPK[selection[0]] except Exception as e: messagebox.showerror('Error During Set', 'Error: ' + str(e)) logging.error(str(e)) def createKiller(self): try: self.assignCurrentCardAsKiller() self.box.selection_clear(0, END) except Exception as e: self.handleCardNotScannedError(e) def assignCurrentCardAsKiller(self): i = self.uuid.index(self.ee.get()) self.uuidFK[i] = int(self.environment.KillCommand) def handleCardNotScannedError(self, e): messagebox.showinfo( 'Card Not Scanned', 'Please scan a card to assign it a [Kill Application] code.' + str(e)) logging.error(str(e)) def load(self): # Generate Log logging.basicConfig(filename=self.LOG_FILE, level=logging.INFO) # Load Sound file self.soundProvider.init() self.soundProvider.mixer.pre_init(44100, -16, 12, 512) # pygame.init() IS this only for linux distribution? self.soundS = self.soundProvider.mixer.Sound(self.environment.SCAN_SOUND) self.soundS.set_volume(1) # Create an instance of the PN532 class. self.RFIDScannerProvider.begin()) # Configure PN532 to communicate with MiFare cards. self.RFIDScannerProvider.SAM_configuration() self.loadFiles() self.locateDevices() self.loadDevice() def loadFiles(self): self.readCSV(self.environment.VideoList, (int, self.vidPK), (str, self.vidNAME), (str, self.vidPATH)) self.readCSV(self.environment.UuidTable, (str, self.uuid), (int, self.uuidFK))
class ListboxVidget(Vidget, Eventor): """ ListboxVidget contains a Listbox widget. It adds the following abilities: - Store items of any type, unlike Listbox widget that only stores texts. - Remember selected item even if the listbox widget lost focus. - Notify pre-change and post-change events. """ # Error raised when trying to change the listbox while a change is going on class CircularCallError(ValueError): pass # Error raised when trying to change the listbox while it is disabled class DisabledError(ValueError): pass # Event notified when the listbox's items are to be changed ITEMS_CHANGE_SOON = 'ITEMS_CHANGE_SOON' # Event notified when the listbox's items are changed ITEMS_CHANGE_DONE = 'ITEMS_CHANGE_DONE' # Event notified when the listbox's active item is to be changed ITEMCUR_CHANGE_SOON = 'ITEMCUR_CHANGE_SOON' # Event notified when the listbox's active item is changed ITEMCUR_CHANGE_DONE = 'ITEMCUR_CHANGE_DONE' # Events list EVENTS = ( ITEMS_CHANGE_SOON, ITEMS_CHANGE_DONE, ITEMCUR_CHANGE_SOON, ITEMCUR_CHANGE_DONE, ) def __init__( self, items=None, item_to_text=None, normal_bg='', normal_fg='', active_bg='sky blue', active_fg='white', selected_bg='steel blue', selected_fg='white', master=None, ): """ Initialize object. @param items: Items list. @param item_to_text: Item-to-text function. Default is `str`. @param normal_bg: Unselected item background color. @param normal_fg: Unselected item foreground color. @param active_bg: Active item background color. `Active` means the item is selected (in general meaning) but the listbox has no focus. @param active_fg: Active item foreground color. `Active` means the item is selected (in general meaning) but the listbox has no focus. @param selected_bg: Selected item background color. `Selected` means the item is selected (in general meaning) and the listbox has focus. @param selected_fg: Selected item foreground color. `Selected` means the item is selected (in general meaning) and the listbox has focus. @param master: Master widget. @return: None. """ # Initialize Vidget. # Create main frame widget. Vidget.__init__( self, master=master, ) # Initialize Eventor Eventor.__init__(self) # If items list is given if items is not None: # If items list is not list if not isinstance(items, list): # Raise error raise TypeError(items) # If items list is list. # If items list is not given, or items list is given and is list # Items list self._items = items if items is not None else [] # Item-to-text function. Default is `str`. self._item_to_text = item_to_text if item_to_text is not None else str # Unselected item background color self._normal_fg = normal_fg # Unselected item foreground color self._normal_bg = normal_bg # Active item background color self._active_fg = active_fg # Active item foreground color self._active_bg = active_bg # Selected item background color self._selected_fg = selected_fg # Selected item foreground color self._selected_bg = selected_bg # Whether the listbox is changing self._is_changing = False # Active index. `-1` means void, i.e. no item is active. self._indexcur = -1 # Whether active index is being reset to same value self._is_resetting = False # Create listbox widget self._listbox = Listbox( master=self.widget(), relief='groove', activestyle='none', highlightthickness=0, # Active index cache only supports single-selection mode for now. # See 2N6OR. selectmode='single', ) # Set the listbox widget as config target self.config_target_set(self._listbox) # Create x-axis scrollbar self._scrollbar_xview = _HiddenScrollbar( self.widget(), orient=HORIZONTAL, ) # Create y-axis scrollbar self._scrollbar_yview = _HiddenScrollbar( self.widget(), orient=VERTICAL, ) # Mount scrollbars self._listbox.config(xscrollcommand=self._scrollbar_xview.set) self._listbox.config(yscrollcommand=self._scrollbar_yview.set) self._scrollbar_xview.config(command=self._listbox.xview) self._scrollbar_yview.config(command=self._listbox.yview) # Bind single-click event handler self._listbox.bind('<Button-1>', self._on_single_click) # Bind double-click event handler self._listbox.bind('<Double-Button-1>', self._on_double_click) # Update listbox widget self._listbox_widget_update(keep_active=False) # Update widget self._widget_update() def _widget_update(self): """ Update widget. @return: None. """ # Row 0 for listbox and y-axis scrollbar self.widget().rowconfigure(0, weight=1) # Row 1 for x-axis scrollbar self.widget().rowconfigure(1, weight=0) # Column 0 for listbox and x-axis scrollbar self.widget().columnconfigure(0, weight=1) # Column 1 for y-axis scrollbar self.widget().columnconfigure(1, weight=0) # Lay out listbox self._listbox.grid(row=0, column=0, sticky='NSEW') # Lay out x-axis scrollbar self._scrollbar_xview.grid(row=1, column=0, sticky='EW') # Lay out y-axis scrollbar self._scrollbar_yview.grid(row=0, column=1, sticky='NS') def is_enabled(self): """ Test whether the listbox is enabled. @return: Boolean. """ # Return whether the listbox is enabled return self._listbox.config('state')[4] != DISABLED def is_changing(self): """ Test whether the listbox is changing. @return: Boolean. """ # Return whether the listbox is changing return self._is_changing def is_resetting(self): """ Test whether the listbox is setting active index to the same value. @return: Boolean. """ # Return whether the listbox is setting active index to the same value return self._is_resetting def size(self): """ Get number of items. @return: Number of items. """ # Return number of items return len(self._items) def items(self): """ Get items list. Notice do not change the list outside. @return: Items list. """ # Return items list return self._items def items_set( self, items, notify=True, keep_active=False, ): """ Set items list. Notice do not change the list outside. @param items: Items list. @param notify: Whether notify pre-change and post-change events. @param keep_active: Whether keep or clear active index. @return: None. """ # If the items is not list if not isinstance(items, list): # Raise error raise TypeError(items) # If the items is list. # If the listbox is disabled if not self.is_enabled(): # Raise error raise ListboxVidget.DisabledError() # If the listbox is not disabled. # If the listbox is changing if self._is_changing: # Raise error raise ListboxVidget.CircularCallError() # If the listbox is not changing. # Set changing flag on self._is_changing = True # If notify events if notify: # Notify pre-change event self.handler_notify(self.ITEMS_CHANGE_SOON) # Store the new items self._items = items # Update listbox widget self._listbox_widget_update( keep_active=keep_active ) # If notify events if notify: # Notify post-change event self.handler_notify(self.ITEMS_CHANGE_DONE) # Set changing flag off self._is_changing = False def index_is_valid(self, index): """ Test whether given index is valid. Notice -1 is not valid. @param index: Index to test. @return: Boolean. """ # Test whether given index is valid return 0 <= index and index < self.size() def index_is_valid_or_void(self, index): """ Test whether given index is valid or is -1. @param index: Index to test. @return: Boolean. """ # Test whether given index is valid or is -1 return index == -1 or self.index_is_valid(index) def index_first(self): """ Get the first item's index. @return: First item's index, or -1 if the listbox is empty. """ # Return the first item's index return 0 if self.size() > 0 else -1 def index_last(self): """ Get the last item's index. @return: Last item's index, or -1 if the listbox is empty. """ # Return the last item's index return self.size() - 1 def indexcur(self, internal=False, raise_error=False): """ Get the active index. @param internal: See 2N6OR. @return: The active index. If no active active, either return -1, or raise IndexError if `raise_error` is True. """ # Get active indexes indexcurs = self._indexcurs(internal=internal) # If have active indexes if indexcurs: # Return the first active index return indexcurs[0] # If no active indexes else: # If raise error if raise_error: # Raise error raise IndexError(-1) # If not raise error else: # Return -1 return -1 def _indexcurs(self, internal=False): """ Get active indexes list. 2N6OR @param internal: Whether use listbox widget's selected indexes, instead of cached active index. Notice listbox widget has no selected indexes if it has no focus. Notice using cached active index only supports single-selection mode, which means the result list has at most one index. @return: Active indexes list. """ # If use listbox widget's selected indexes if internal: # Return listbox widget's selected indexes list return [int(x) for x in self._listbox.curselection()] # If not use listbox widget's selected indexes else: # If cached active index is valid if self.index_is_valid(self._indexcur): # Return a list with the cached active index return [self._indexcur] # If cached active index is not valid else: # Return empty list return [] def indexcur_set( self, index, focus=False, notify=True, notify_arg=None, ): """ Set active index. @param index: The index to set. @param focus: Whether set focus on the listbox widget. @param notify: Whether notify pre-change and post-change events. @param notify_arg: Event argument. @return: None. """ # If the index is not valid or -1 if not self.index_is_valid_or_void(index): # Raise error raise IndexError(index) # If the index is valid or is -1. # If the listbox is not enabled if not self.is_enabled(): # Raise error raise ListboxVidget.DisabledError() # If the listbox is enabled. # If the listbox is changing if self._is_changing: # Raise error raise ListboxVidget.CircularCallError() # If the listbox is not changing. # Set changing flag on self._is_changing = True # Get old active index old_indexcur = self._indexcur # Set resetting flag on if new and old indexes are equal self._is_resetting = (index == old_indexcur) # If notify events if notify: # Notify pre-change event self.handler_notify(self.ITEMCUR_CHANGE_SOON, notify_arg) # If old active index is valid if self.index_is_valid(old_indexcur): # Set old active item's background color to normal color self._listbox.itemconfig(old_indexcur, background=self._normal_bg) # Set old active item's foreground color to normal color self._listbox.itemconfig(old_indexcur, foreground=self._normal_fg) # Cache new active index self._indexcur = index # Clear listbox widget's selection self._listbox.selection_clear(0, END) # Set listbox widget's selection self._listbox.selection_set(index) # Set listbox widget's activated index self._listbox.activate(index) # If new active index is valid if index != -1: # Set new active item's background color to active color self._listbox.itemconfig(index, background=self._active_bg) # Set new active item's foreground color to active color self._listbox.itemconfig(index, foreground=self._active_fg) # If set focus if focus: # Set focus on the listbox widget self._listbox.focus_set() # If new active index is valid if index != -1: # Make the active item visible self._listbox.see(index) # If notify events if notify: # Notify post-change event self.handler_notify(self.ITEMCUR_CHANGE_DONE, notify_arg) # Set resetting flag off self._is_resetting = False # Set changing flag off self._is_changing = False def indexcur_set_by_event( self, event, focus=False, notify=True, notify_arg=None, ): """ Set active index using a Tkinter event object that contains coordinates of the active item. @param event: Tkinter event object. @param focus: Whether set focus on the listbox widget. @param notify: Whether notify pre-change and post-change events. @param notify_arg: Event argument. @return: None. """ # Get the event's y co-ordinate's nearest listbox item index index = self._listbox.nearest(event.y) # If the index is not valid if not self.index_is_valid_or_void(index): # Ignore the event return # If the index is valid else: # Set the index as active index self.indexcur_set( index=index, focus=focus, notify=notify, notify_arg=notify_arg, ) def item(self, index): """ Get item at given index. @return: Item at given index, or IndexError if the index is not valid. """ return self.items()[index] def itemcur(self, internal=False, raise_error=False): """ Get the active item. @param internal: See 2N6OR. @param raise_error: Whether raise error if no active item. @return: The active item. If no active item, if `raise_error` is True, raise IndexError, otherwise return None. """ # Get active index. # May raise IndexError if `raise_error` is True. indexcur = self.indexcur( internal=internal, raise_error=raise_error, ) # If no active index if indexcur == -1: # Return None return None # If have active index else: # Return the active item return self.items()[indexcur] def item_insert( self, item, index=None, notify=True, keep_active=True, ): """ Insert item at given index. @param item: Item to insert. @param index: Index to insert. `None` means active index, and if no active index, insert at the end. @param notify: Whether notify pre-change and post-change events. @param keep_active: Whether keep or clear active index. @return: None. """ # If notify events if notify: # Notify pre-change events self.handler_notify(self.ITEMCUR_CHANGE_SOON) self.handler_notify(self.ITEMS_CHANGE_SOON) # Get old active index active_index = self.indexcur() # If the index is None, # it means use active index. if index is None: # Use active index. # `-1` works and means appending. index = active_index # Insert the item to the items list self._items.insert(index, item) # If old active index is valid if active_index != -1: # If old active index is GE the inserted index if active_index >= index: # Shift active index by one active_index += 1 # If old active index is not GE the inserted index, use it as-is. # Set new active index self.indexcur_set(index=active_index, notify=False) # Update listbox widget self._listbox_widget_update( keep_active=keep_active ) # If notify events if notify: # Notify post-change events self.handler_notify(self.ITEMS_CHANGE_DONE) self.handler_notify(self.ITEMCUR_CHANGE_DONE) def item_remove( self, index, notify=True, keep_active=True, ): """ Remove item at given index. @param index: Index to remove. @param notify: Whether notify pre-change and post-change events. @param keep_active: Whether keep or clear active index. @return: None. """ # If the index is not valid if not self.index_is_valid(index): # Raise error raise ValueError(index) # If the index is valid. # If notify events if notify: # Notify pre-change events self.handler_notify(self.ITEMCUR_CHANGE_SOON) self.handler_notify(self.ITEMS_CHANGE_SOON) # Get old active index active_index = self.indexcur() # Remove item at the index del self._items[index] # If old active index is valid if active_index != -1: # Get the last index index_last = self.index_last() # If old active index is GT the last index if active_index > index_last: # Use the last index as new active index active_index = index_last # If old active index is not GT the last index, use it as-is. # Set new active index self.indexcur_set(index=active_index, notify=False) # Update listbox widget self._listbox_widget_update( keep_active=keep_active ) # If notify events if notify: # Notify post-change events self.handler_notify(self.ITEMS_CHANGE_DONE) self.handler_notify(self.ITEMCUR_CHANGE_DONE) def handler_add( self, event, handler, need_arg=False, ): """ Add event handler for an event. If the event is ListboxVidget event, add the event handler to Eventor. If the event is not ListboxVidget event, add the event handler to listbox widget. Notice this method overrides `Eventor.handler_add` in order to add non-ListboxVidget event handler to listbox widget. @param event: Event name. @param handler: Event handler. @param need_arg: Whether the event handler needs event argument. @return: None. """ # If the event is ListboxVidget event if event in self.EVENTS: # Add the event handler to Eventor return Eventor.handler_add( self, event=event, handler=handler, need_arg=need_arg, ) # If the event is not ListboxVidget event, # it is assumed to be Tkinter widget event. else: # Add the event handler to listbox widget return self.bind( event=event, handler=handler, ) def bind( self, event, handler, ): """ Add event handler to listbox widget. ListboxVidget internally uses `<Button-1>` and `<Double-Button-1>` to capture active index changes. So if the given event is `<Button-1>` or `<Double-Button-1>`, the given handler will be wrapped. @param event: Event name. @param handler: Event handler. @return: None. """ # If the event is not `<Button-1>` or `<Double-Button-1>` if event not in ['<Button-1>', '<Double-Button-1>']: # Add the event handler to listbox widget self._listbox.bind(event, handler) # If the event is `<Button-1>` or `<Double-Button-1>` else: # Create event handler wrapper def handler_wrapper(e): """ Event handler wrapper that sets new active index and then calls the wrapped event handler. Setting new active index is needed because when this handler is called by Tkinter, the active index of the listbox is still old. @param e: Tkinter event object. @return: None. """ # Set new active index self.indexcur_set_by_event(e, notify=True) # Call the wrapped event handler handler(e) # Add the event handler wrapper to the listbox widget self._listbox.bind(event, handler_wrapper) def _on_single_click(self, event): """ `<Button-1>` event handler that updates active index. @param event: Tkinter event object. @return: None. """ # Updates active index self.indexcur_set_by_event(event, notify=True) def _on_double_click(self, event): """ `<Double-Button-1>` event handler that updates active index. @param event: Tkinter event object. @return: None. """ # Updates active index self.indexcur_set_by_event(event, notify=True) def _listbox_widget_update( self, keep_active, ): """ Update listbox widget's items and selection. @param keep_active: Whether keep or clear active index. @return: None. """ # Remove old items from listbox widget self._listbox.delete(0, END) # Insert new items into listbox widget. # For each ListboxVidget items. for index, item in enumerate(self.items()): # Get item text item_text = self._item_to_text(item) # Insert the item text into listbox widget self._listbox.insert(index, item_text) # Set the item's normal background color self._listbox.itemconfig(index, background=self._normal_bg) # Set the item's normal foreground color self._listbox.itemconfig(index, foreground=self._normal_fg) # Set the item's selected background color self._listbox.itemconfig(index, selectbackground=self._selected_bg) # Set the item's selected foreground color self._listbox.itemconfig(index, selectforeground=self._selected_fg) # If keep active index if keep_active: # Use old active index indexcur = self._indexcur # If not keep active index else: # Set active index to -1 indexcur = self._indexcur = -1 # Clear old selection self._listbox.selection_clear(0, END) # Set new selection. # `-1` works. self._listbox.selection_set(indexcur) # Set new active index. # `-1` works. self._listbox.activate(indexcur) # If new active index is valid if indexcur != -1: # Set active background color self._listbox.itemconfig(indexcur, background=self._active_bg) # Set active foreground color self._listbox.itemconfig(indexcur, foreground=self._active_fg) # Make the active item visible self._listbox.see(indexcur)
class AutocompleteEntry(Entry): def __init__(self, lista, *args, **kwargs): Entry.__init__(self, *args, **kwargs) self.lista = lista self.var = self["textvariable"] if self.var == '': self.var = self["textvariable"] = StringVar() self.var.trace('w', self.changed) self.bind("<Right>", self.selection) self.bind("<Up>", self.up) self.bind("<Down>", self.down) self.lb_up = False def changed(self, name, index, mode): if self.var.get() == '': self.lb.destroy() self.lb_up = False else: words = self.comparison() if words: if not self.lb_up: self.lb = Listbox() self.lb.bind("<Double-Button-1>", self.selection) self.lb.bind("<Right>", self.selection) self.lb.place(x=self.winfo_x(), y=self.winfo_y() + self.winfo_height()) self.lb_up = True self.lb.delete(0, END) for w in words: self.lb.insert(END, w) else: if self.lb_up: self.lb.destroy() self.lb_up = False def selection(self, event): if self.lb_up: self.var.set(self.lb.get(ACTIVE)) self.lb.destroy() self.lb_up = False self.icursor(END) def up(self, event): if self.lb_up: if self.lb.curselection() == (): index = '0' else: index = self.lb.curselection()[0] if index != '0': self.lb.selection_clear(first=index) index = str(int(index) - 1) self.lb.selection_set(first=index) self.lb.activate(index) def down(self, event): if self.lb_up: if self.lb.curselection() == (): index = '0' else: index = self.lb.curselection()[0] if index != END: self.lb.selection_clear(first=index) index = str(int(index) + 1) self.lb.selection_set(first=index) self.lb.activate(index) def comparison(self): pattern = re.compile('.*' + self.var.get() + '.*') return [w for w in self.lista if re.match(pattern, w)]
class TPolygonWindow(simpledialog.Dialog): """ Class represents a polygon vertices edit window. :param master: master window object. :type master: tkinter.Tk :param app: main app object. :type app: TApp :param polygon: edited polygon object. :type polygon: TPolygon """ def __init__(self, master, app, polygon): """ Initialise object variables and call the parent class constructor. """ self._app = app self._polygon = polygon super().__init__(master) def body(self, master): """ Initialise widgets. :param master: master window object. :type master: tkinter.Tk """ # Frame for widgets self.main_frame = Frame(self) # Listbox self.vertices_list = Listbox(self.main_frame, exportselection = False, \ width = 40) self.vertices_list.config(exportselection=0) self.vertices_list.pack(expand=True, fill=BOTH, side=LEFT) self.vertices_list.bind("<Double-Button-1>", self.vertices_list_selected_item) # Listbox's yscrollbar self.vertices_list_scrollbar = Scrollbar(self.main_frame, orient = VERTICAL, \ command = self.vertices_list.yview) self.vertices_list_scrollbar.pack(expand=True, fill=Y, side=LEFT) self.vertices_list.config( yscrollcommand=self.vertices_list_scrollbar.set) self.main_frame.pack(expand=True, fill=BOTH) # Fill list with vertices data self.update_vertices_list() def buttonbox(self): """ Redefine default Ok/Cancel buttons in the bottom of the window with Apply/Add/Delete. """ self.bbox = Frame(self) self.apply_button = Button(self.bbox, text = "Apply", width = 10, \ command = self.apply) self.apply_button.pack(side=LEFT, padx=5, pady=5) self.add_button = Button(self.bbox, text = "Add", width = 10, \ command = self.add_vertex) self.add_button.pack(side=LEFT, padx=5, pady=5) self.delete_button = Button(self.bbox, text = "Delete", width = 10, \ command = self.delete_vertex) self.delete_button.pack(side=LEFT, padx=5, pady=5) self.bbox.pack() def get_current_selection(self): """ Retrieve the selected vertex index. :rtype: integer """ try: cursel = self.vertices_list.curselection()[0] except: cursel = 0 return cursel def vertices_list_selected_item(self, event): """ Display and edit selected vertex parameters. :param event: listbox LMB click event. :type event: tkinter.Event """ try: vertex_num = (self.vertices_list.curselection())[0] except IndexError: return except Exception as message: messagebox.showerror("Error while picking shape!", message) if (vertex_num < 0): return else: try: vertex = self._polygon.points_mod[vertex_num] except Exception as message: messagebox.showerror("Materials list error", message) return initialvalue = str(vertex.x) + " " + str(vertex.y) input_str = simpledialog.askstring("Input coordinates", "Give vertex coordinates", \ initialvalue = initialvalue) try: new_x, new_y = input_str.split() except AttributeError: pass except ValueError as e: messagebox.showerror("Wrong input!", e) else: edited_point = self._polygon.points_mod[vertex_num] edited_point.x, edited_point.y = float(new_x), float(new_y) self._polygon.update_window_positions() self._app.main_canvas.delete("all") self._app.canvas_refresh() finally: self.update_vertices_list() self.vertices_list.select_clear(0, END) self.vertices_list.selection_set(vertex_num) self.vertices_list.activate(vertex_num) self.vertices_list.focus_set() def update_vertices_list(self): """ Update entries in the vertices listbox. """ cursel = self.get_current_selection() self.vertices_list.delete(0, END) for i, v in enumerate(self._polygon.points_mod): self.vertices_list.insert(i, str(i + 1) + ". (" + str(v.x) + ", " + \ str(v.y) + ")") self.vertices_list.select_clear(0, END) if (cursel >= self.vertices_list.size()): self.vertices_list.selection_set(cursel - 1) self.vertices_list.activate(cursel) else: self.vertices_list.selection_set(cursel) self.vertices_list.activate(cursel) def add_vertex(self): """ Add a vertex to the polygon. """ cursel = self.get_current_selection() input_str = simpledialog.askstring("Input coordinates", "Give vertex coordinates") try: new_x, new_y = input_str.split() except AttributeError: pass except ValueError as e: messagebox.showerror("Wrong input", e) else: self._polygon.add_vertex(x_mod=float(new_x), y_mod=float(new_y)) self._app.main_canvas.delete("all") self._app.canvas_refresh() finally: self.update_vertices_list() self.vertices_list.select_clear(0, END) self.vertices_list.selection_set(cursel) self.vertices_list.activate(cursel) self.vertices_list.focus_set() def delete_vertex(self): """ Delete a vertex from the polygon. """ cursel = self.get_current_selection() self._polygon.remove_vertex(cursel) self._app.main_canvas.delete("all") self._app.canvas_refresh() self.update_vertices_list() self.vertices_list.select_clear(0, END) self.vertices_list.selection_set(cursel) self.vertices_list.activate(cursel) self.vertices_list.focus_set() def apply(self): """ Destroy window upon clicking Apply button. """ self.destroy()
class myListBox(Frame): def __init__(self, root, items=[], id='', item_select_handler=default_item_select_handler, smode=tki.EXTENDED): self.item_count = 0 self.root = root self.item_select_handler = item_select_handler Frame.__init__(self, self.root) self.id = id vscrollbar = Scrollbar(self, orient=tki.VERTICAL) vscrollbar.pack(side=tki.RIGHT, fill=tki.Y) hscrollbar = Scrollbar(self, orient=tki.HORIZONTAL) hscrollbar.pack(side=tki.BOTTOM, fill=tki.X) ## mode can be: SINGLE, BROWSE, MULTIPLE, EXTENDED ## selectmode ## ## Determines how many items can be selected, and how mouse drags affect the selection − ## ## BROWSE − Normally, you can only select one line out of a listbox. If you click on an item and then drag to a different line, the selection will follow the mouse. This is the default. ## SINGLE − You can only select one line, and you can't drag the mouse.wherever you click button 1, that line is selected. ## MULTIPLE − You can select any number of lines at once. Clicking on any line toggles whether or not it is selected. ## EXTENDED − You can select any adjacent group of lines at once by clicking on the first line and dragging to the last line. self.list = Listbox(self, selectmode=smode, exportselection=0, xscrollcommand=hscrollbar.set, yscrollcommand=vscrollbar.set) for i in items: assert (type(i) is str) self.list.insert(items.index(i), i) self.list.pack(fill=tki.BOTH, expand=1) self.list.bind('<Double-Button-1>', self.item_select_handler) self.list.bind('<1>', self.item_select_handler) self.list.bind('<Return>', self.item_select_handler) ## DO NOT catch ListboxSelect event, because: ## a) it is not associated with (x_root, y_root) and (x,y) coordinates, so the popup appears always at (0,0) of the main root window ## b) it duplicates the click event catching self.list.bind( '<1>', self.item_select_handler ) and generates a second event ## self.list.bind( '<<ListboxSelect>>', self.item_select_handler ) hscrollbar.config(command=self.list.xview) vscrollbar.config(command=self.list.yview) self.pack(side=tki.LEFT, fill=tki.BOTH, expand=1) self.current = self.list.curselection() def insert_item(self, pos, item): self.list.insert(pos, item) self.item_count += 1 self.activate(pos) self.index(pos) def delete(self, start, end=None): assert (type(start) is int) if (end is None): self.list.delete(start) self.item_count -= 1 else: assert ((type(end) is int) or (end == tki.END)) if (type(end) is str): self.list.delete(start, (self.item_count - 1)) self.item_count -= (self.item_count - start) else: self.list.delete(start, end) self.item_count -= (end - start) + 1 def select_set(self, i): self.list.selection_clear(0, tki.END) self.list.select_set(i) def activate(self, i): self.list.activate(i) def index(self, i): self.list.index(i) def generate_select_event(self, pos=None): assert (pos is not None) if (pos is not None): self.activate(pos) self.index(pos) self.select_set(pos) self.list.event_generate("<<ListboxSelect>>") ## def poll(self): ## now = self.list.curselection() ## if now != self.current: ## self.list_has_changed( now ) ## self.current = now ## self.after( 250, self.poll ) def list_has_changed(self, selection): print('widget {0} selection is {1}'.format(self.id, selection))
class Combobox_Autocomplete(Entry, object): def __init__(self, master, list_of_items=None, autocomplete_function=None, listbox_width=None, listbox_height=7, ignorecase_match=False, startswith_match=True, vscrollbar=True, hscrollbar=True, **kwargs): if hasattr(self, "autocomplete_function"): if autocomplete_function is not None: raise ValueError( "Combobox_Autocomplete subclass has 'autocomplete_function' implemented" ) else: if autocomplete_function is not None: self.autocomplete_function = autocomplete_function else: if list_of_items is None: raise ValueError( "If not guiven complete function, list_of_items can't be 'None'" ) if ignorecase_match: if startswith_match: def matches_function(entry_data, item): return item.startswith(entry_data) else: def matches_function(entry_data, item): return item in entry_data self.autocomplete_function = lambda entry_data: [ item for item in self.list_of_items if matches_function(entry_data, item) ] else: if startswith_match: def matches_function(escaped_entry_data, item): if re.match(escaped_entry_data, item, re.IGNORECASE): return True else: return False else: def matches_function(escaped_entry_data, item): if re.search(escaped_entry_data, item, re.IGNORECASE): return True else: return False def autocomplete_function(entry_data): escaped_entry_data = re.escape(entry_data) return [ item for item in self.list_of_items if matches_function(escaped_entry_data, item) ] self.autocomplete_function = autocomplete_function self._listbox_height = int(listbox_height) self._listbox_width = listbox_width self.list_of_items = list_of_items self._use_vscrollbar = vscrollbar self._use_hscrollbar = hscrollbar kwargs.setdefault("background", "white") if "textvariable" in kwargs: self._entry_var = kwargs["textvariable"] else: self._entry_var = kwargs["textvariable"] = StringVar() Entry.__init__(self, master, **kwargs) self._trace_id = self._entry_var.trace('w', self._on_change_entry_var) self._listbox = None self.bind("<Tab>", self._on_tab) self.bind("<Up>", self._previous) self.bind("<Down>", self._next) self.bind('<Control-n>', self._next) self.bind('<Control-p>', self._previous) self.bind("<Return>", self._update_entry_from_listbox) self.bind("<Escape>", lambda event: self.unpost_listbox()) def _on_tab(self, event): self.post_listbox() return "break" def _on_change_entry_var(self, name, index, mode): entry_data = self._entry_var.get() if entry_data == '': self.unpost_listbox() self.focus() else: values = self.autocomplete_function(entry_data) if values: if self._listbox is None: self._build_listbox(values) else: self._listbox.delete(0, END) height = min(self._listbox_height, len(values)) self._listbox.configure(height=height) for item in values: self._listbox.insert(END, item) else: self.unpost_listbox() self.focus() def _build_listbox(self, values): listbox_frame = Frame() self._listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none", exportselection=False) self._listbox.grid(row=0, column=0, sticky=N + E + W + S) self._listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox) self._listbox.bind("<Return>", self._update_entry_from_listbox) self._listbox.bind("<Escape>", lambda event: self.unpost_listbox()) self._listbox.bind('<Control-n>', self._next) self._listbox.bind('<Control-p>', self._previous) if self._use_vscrollbar: vbar = Scrollbar(listbox_frame, orient=VERTICAL, command=self._listbox.yview) vbar.grid(row=0, column=1, sticky=N + S) self._listbox.configure( yscrollcommand=lambda f, l: autoscroll(vbar, f, l)) if self._use_hscrollbar: hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command=self._listbox.xview) hbar.grid(row=1, column=0, sticky=E + W) self._listbox.configure( xscrollcommand=lambda f, l: autoscroll(hbar, f, l)) listbox_frame.grid_columnconfigure(0, weight=1) listbox_frame.grid_rowconfigure(0, weight=1) x = -self.cget("borderwidth") - self.cget("highlightthickness") y = self.winfo_height() - self.cget("borderwidth") - self.cget( "highlightthickness") if self._listbox_width: width = self._listbox_width else: width = self.winfo_width() listbox_frame.place(in_=self, x=x, y=y, width=width) height = min(self._listbox_height, len(values)) self._listbox.configure(height=height) for item in values: self._listbox.insert(END, item) def post_listbox(self): if self._listbox is not None: return entry_data = self._entry_var.get() if entry_data == '': return values = self.autocomplete_function(entry_data) if values: self._build_listbox(values) def unpost_listbox(self): if self._listbox is not None: self._listbox.master.destroy() self._listbox = None def get_value(self): return self._entry_var.get() def set_value(self, text, close_dialog=False): self._set_var(text) if close_dialog: self.unpost_listbox() self.icursor(END) self.xview_moveto(1.0) def _set_var(self, text): self._entry_var.trace_vdelete("w", self._trace_id) self._entry_var.set(text) self._trace_id = self._entry_var.trace('w', self._on_change_entry_var) def _update_entry_from_listbox(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if current_selection: text = self._listbox.get(current_selection) self._set_var(text) self._listbox.master.destroy() self._listbox = None self.focus() self.icursor(END) self.xview_moveto(1.0) return "break" def _previous(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if len(current_selection) == 0: self._listbox.selection_set(0) self._listbox.activate(0) else: index = int(current_selection[0]) self._listbox.selection_clear(index) if index == 0: index = END else: index -= 1 self._listbox.see(index) self._listbox.selection_set(first=index) self._listbox.activate(index) return "break" def _next(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if len(current_selection) == 0: self._listbox.selection_set(0) self._listbox.activate(0) else: index = int(current_selection[0]) self._listbox.selection_clear(index) if index == self._listbox.size() - 1: index = 0 else: index += 1 self._listbox.see(index) self._listbox.selection_set(index) self._listbox.activate(index) return "break" # if __name__ == '__main__': # try: # from Tkinter import Tk # except ImportError: # from tkinter import Tk # # list_of_items = ["Cordell Cannata", "Lacey Naples", "Zachery Manigault", "Regan Brunt", "Mario Hilgefort", "Austin Phong", "Moises Saum", "Willy Neill", "Rosendo Sokoloff", "Salley Christenberry", "Toby Schneller", "Angel Buchwald", "Nestor Criger", "Arie Jozwiak", "Nita Montelongo", "Clemencia Okane", "Alison Scaggs", "Von Petrella", "Glennie Gurley", "Jamar Callender", "Titus Wenrich", "Chadwick Liedtke", "Sharlene Yochum", "Leonida Mutchler", "Duane Pickett", "Morton Brackins", "Ervin Trundy", "Antony Orwig", "Audrea Yutzy", "Michal Hepp", "Annelle Hoadley", "Hank Wyman", "Mika Fernandez", "Elisa Legendre", "Sade Nicolson", "Jessie Yi", "Forrest Mooneyhan", "Alvin Widell", "Lizette Ruppe", "Marguerita Pilarski", "Merna Argento", "Jess Daquila", "Breann Bevans", "Melvin Guidry", "Jacelyn Vanleer", "Jerome Riendeau", "Iraida Nyquist", "Micah Glantz", "Dorene Waldrip", "Fidel Garey", "Vertie Deady", "Rosalinda Odegaard", "Chong Hayner", "Candida Palazzolo", "Bennie Faison", "Nova Bunkley", "Francis Buckwalter", "Georgianne Espinal", "Karleen Dockins", "Hertha Lucus", "Ike Alberty", "Deangelo Revelle", "Juli Gallup", "Wendie Eisner", "Khalilah Travers", "Rex Outman", "Anabel King", "Lorelei Tardiff", "Pablo Berkey", "Mariel Tutino", "Leigh Marciano", "Ok Nadeau", "Zachary Antrim", "Chun Matthew", "Golden Keniston", "Anthony Johson", "Rossana Ahlstrom", "Amado Schluter", "Delila Lovelady", "Josef Belle", "Leif Negrete", "Alec Doss", "Darryl Stryker", "Michael Cagley", "Sabina Alejo", "Delana Mewborn", "Aurelio Crouch", "Ashlie Shulman", "Danielle Conlan", "Randal Donnell", "Rheba Anzalone", "Lilian Truax", "Weston Quarterman", "Britt Brunt", "Leonie Corbett", "Monika Gamet", "Ingeborg Bello", "Angelique Zhang", "Santiago Thibeau", "Eliseo Helmuth"] # # root = Tk() # root.geometry("300x200") # # combobox_autocomplete = Combobox_Autocomplete(root, list_of_items, highlightthickness=1) # combobox_autocomplete.pack() # # combobox_autocomplete.focus() # # root.mainloop()
class Pdf_converter: def __init__(self): self._root = Tk() self._root.geometry("500x400") self._root.title("IMGPDF") self._Elements() Grid.rowconfigure(self._root, 0, weight=1) Grid.columnconfigure(self._root, 0, weight=1) self.supportedfiles = [ ('PDF files', '*.pdf*'), ('jpg files', '*.jpg*'), ('png files', '*.png*'), ('gif files', '*.gif*'), ('All files', '*.*') ] self.openfiles = [] self._root.mainloop() def _Elements(self): ##create button self._create = Button(self._root, text="Create", command=self._Create_pdf) ##lift up self._lift = Button(self._root, text="↑ Lift Up", command=self._Lift_up) ##push down self._push = Button(self._root, text="↓ Push Down", command=self._Push_down) ##addfiles self._addfiles = Button(self._root, text="Add Files", command=self._Select_files) ##scrollbars self._hscroll = Scrollbar(self._root, orient=HORIZONTAL) self._vscroll = Scrollbar(self._root) ##Listwidget / List box self._listbox = Listbox(self._root, width=40, xscrollcommand=self._hscroll.set, yscrollcommand=self._vscroll.set) self._hscroll.config(command=self._listbox.xview) self._vscroll.config(command=self._listbox.yview) #packing self._listbox.grid(column=0, columnspan=4, row=0, rowspan=6, sticky=N + E + W + S) self._hscroll.grid(column=0, row=6, columnspan=4, sticky=W + E) self._vscroll.grid(column=4, row=0, rowspan=6, sticky=N + S) self._addfiles.grid(column=3, row=7, sticky=W + E) self._create.grid(column=2, row=7, sticky=W + E) self._push.grid(column=5, row=1, sticky=S + E + W) self._lift.grid(column=5, row=0, sticky=N + W + E) def _Lift_up(self): #"to lift the element up." try: current = self._listbox.curselection()[0] value = self._listbox.get(current) if current != 0: upper = current - 1 self._listbox.insert(current, self._listbox.get(upper)) self._listbox.delete(current + 1) self._listbox.insert(upper, value) self._listbox.delete(upper + 1) self._listbox.selection_set(upper) self._listbox.activate(upper) except: print("Error in Lift Up") def _Push_down(self): #"to push the item down." try: current = self._listbox.curselection()[0] value = self._listbox.get(current) if current < self._listbox.size() - 1: lower = current + 1 self._listbox.insert(current, self._listbox.get(lower)) self._listbox.delete(current + 1) self._listbox.insert(lower, value) self._listbox.delete(lower + 1) self._listbox.selection_set(lower) self._listbox.activate(lower) except: print("Error in Push Down") def _Select_files(self): names = askopenfilename(defaultextension=".pdf", filetypes=self.supportedfiles, multiple=True) for file in names: self._listbox.insert(END, file) def _Create_pdf(self): #"To create pdf of the images and join then in the given sequence." self.final = asksaveasfilename(initialfile="merged.pdf", defaultextension=".pdf", filetypes=[('PDF files', '*.pdf*')]) self._merger() def _merger(self): file_list = self._listbox.get(0, END) newfile = self.final[:-4] + '0.pdf' if file_list[0][-4:] == '.jpg' or file_list[0][ -4:] == '.png' or file_list[0][-4:] == '.gif': fname = file_list[0][:-4] + '.pdf' image = Image.open(file_list[0]) pdf = image.convert('RGB') pdf.save(newfile) image.close() elif file_list[0][-4:] == '.pdf': pdf = open(file_list[0], 'rb') image = pdf.read() pdf.close() pdf = open(newfile, 'wb') pdf.write(image) pdf.close() for i in range(len(file_list)): if i != 0: pdfname = self.final[:-4] + '_' + str(i) + '.pdf' if file_list[i][-4:] == '.jpg' or file_list[i][ -4:] == '.png' or file_list[i][-4:] == '.gif': fname = file_list[i][:-4] + '.pdf' image = Image.open(file_list[i]) pdf = image.convert('RGB') pdf.save(pdfname) image.close() elif file_list[i][-4:] == '.pdf': pdf = open(file_list[i], 'rb') image = pdf.read() pdf.close() pdf = open(pdfname, 'wb') pdf.write(image) pdf.close() pdflist = [newfile, pdfname] merger = PdfFileMerger() for pdfs in pdflist: pdf = (open(pdfs, 'rb')) self.openfiles.append(pdf) merger.append(self.openfiles[-1]) newfile = newfile = self.final[:-4] + str(i) + '.pdf' with open(newfile, 'wb') as f: merger.write(f) self.openfiles[-1].close() self.openfiles[-2].close() remove(pdfname) remove(self.final[:-4] + str(i - 1) + '.pdf') pdf = open(newfile, 'rb') data = pdf.read() pdf.close() remove(newfile) pdf = open(self.final, 'wb') pdf.write(data) pdf.close()
class App(tkinter.Tk): def __init__(self, parent, app_dir): tkinter.Tk.__init__(self, parent) self.parent = parent # var declaration self.app_dir = app_dir self.filenames = [] # tyoe: list[str] self.file_list = None # type: tkinter.Listbox self.output = os.path.join(app_dir, 'output.pdf') # type: str # init self.initialize() def initialize(self): # set the app size self.geometry("{}x{}".format(WIDTH, HEIGHT)) # create frames left_frame = Frame(self, bg='cyan', width=WIDTH // 2, height=HEIGHT, pady=3) right_frame = Frame(self, bg='lavender', width=WIDTH // 2, height=HEIGHT, pady=3) # layout all main containers with some scale self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) # stick frames to positions (side by side) left_frame.grid(row=0, column=0, sticky='nsew') right_frame.grid(row=0, column=1, sticky='nsew') # create widgets label_pdf = Label(left_frame, text='PDFs') self.file_list = Listbox(left_frame) label_cmds = Label(right_frame, text='Commands') button_import = tkinter.Button(right_frame, text='Import', command=self._get_files) button_up = tkinter.Button(right_frame, text='up', command=self._list_move_up) button_down = tkinter.Button(right_frame, text='Down', command=self._list_move_down) button_remove = tkinter.Button(right_frame, text='remove', command=self._list_remove) button_merge = tkinter.Button(right_frame, text='Merge!', command=self._merge) # layout widgets label_pdf.grid(row=0, column=0) self.file_list.grid(row=1, column=0) label_cmds.grid(row=0, column=1) button_import.grid(row=1, column=1) button_up.grid(row=2, column=1) button_down.grid(row=3, column=1) button_remove.grid(row=4, column=1) button_merge.grid(row=5, column=1) self.update_listbox() def insert_temp_list(self): """ Temp list for debugging """ self.filenames = ['cat.pdf', 'dog.pdf', 'meow.pdf'] self.update_listbox() def _get_files(self): """ Prompt the user for the files that they wish to merge. All files are added to a list """ # a list of filetype tuples (filename, extension) filetypes = [("pdf files", "*.pdf")] # Read in the selected files filenames = filedialog.askopenfilenames( initialdir=self.app_dir, title="Select pdf's to merge. Ctrl + click for multiple", filetypes=filetypes) # add files for filename in filenames: self.filenames.append(filename) self.update_listbox() def update_listbox(self): """ Redraws the listbox to show self.filenames """ # delete all elements from list self.file_list.delete(0, tkinter.END) # add list items. last name only for filename in self.filenames: self.file_list.insert(tkinter.END, os.path.basename(filename)) def _list_move_up(self): """ Move the selected element up if possible """ index = self.get_selected_index() if (index is not None) and (index != 0): up = index - 1 # swap elements upwards list_swap(self.filenames, index, up) self.update_listbox() # set the cursor position within the list self.select_line(up) def _list_move_down(self): """ Move the selected element down if possible """ index = self.get_selected_index() if (index is not None) and (index != len(self.filenames) - 1): down = index + 1 # swap elements downwards list_swap(self.filenames, index, down) self.update_listbox() # set the cursor position within the list self.select_line(down) def _list_remove(self): """ Remove the selected item * if not selected, nothing happens * if possible the selection cursor does not move * if you are removing the latest element the selection moves back one * if you remove the last element, nothing is selected """ index = self.get_selected_index() # ensure something is selected if index is None: return # remove element self.filenames.pop(index) self.update_listbox() # work out where the cursor should be max_pos = len(self.filenames) if index < max_pos: self.select_line(index) elif max_pos != 0: self.select_line(index - 1) def select_line(self, index): """ select a line within self.file_list if the index makes sense """ if index < len(self.filenames): self.file_list.activate(index) self.file_list.select_set(index) def get_selected_index(self): """ Return the index of the element selected in self.file_list. None if nothing is selected """ selected = self.file_list.curselection() if len(selected) > 0: return selected[0] return None def _merge(self): """ Callback for when files all pdf files are ready to be merged """ # check if any pdfs have been selected if len(self.filenames) == 0: print('No files have been selected. Nothing that can be done') return # prompt the user for the output file name self.set_output_file_name() # Run merge for all the files merge_all(self.filenames, self.output) def set_output_file_name(self): """ Prompts the user to select the output file name """ # a list of filetype tuples (filename, extension) filetypes = [("pdf files", "*.pdf")] # Read in the user desired file location output = filedialog.asksaveasfilename(initialdir=self.app_dir, title="Output merged", filetypes=filetypes) # check if the extension is actually pdf (fix shorthanding) self.output = output if output.endswith('pdf') else (output + '.pdf')
class Player(Frame): def __init__(self,master): super().__init__(master) self.master =master mixer.init() self.pack() if os.path.exists("tracks.pickle"): with open ("tracks.pickle", "rb") as f: self.playlist =pickle.load(f) else: self.playlist =[] # player state flags self.track_index =0 self.track_paused =True self.track_played =False self.model_frame() self.track_widget() self.tracklist_widget() self.controls_widget() # frames def model_frame(self): self.track =LabelFrame(self, relief =GROOVE ) self.track.configure( width =410, height =300 ) self.track.grid(row=0, column=0,pady=10,padx=10) self.tracklist =LabelFrame(self, relief =GROOVE ) self.tracklist.configure( width =190, height =420 ) self.tracklist.grid(row=0, column=1,rowspan=3,pady=10,padx=10) self.controls =LabelFrame(self, font =("times new roman",15,"bold"), bg ="white", fg ="white", bd =0, relief =GROOVE ) self.controls.configure( width =410, height =100 ) self.controls.grid(row=1, column=0,pady=10,padx=10) # widgets def track_widget(self): self.canvas =Label(self.track,font =("times new roman",15,"bold"),bg="white", fg ="dodgerblue") self.canvas['text'] ="Hit tracks"; self.canvas.configure(width =33, height =1) self.canvas.grid(row =0,column =0) self.canvas =Label(self.track,image=track_ico) self.canvas.configure(width =300, height =240) self.canvas.grid(row =1,column =0) self.playing_tune =Label(self.track,font =("Calibri",13),bg="white", fg ="dodgerblue") self.playing_tune['text'] ="Musiflix MP3 Player"; self.playing_tune.configure(width =44, height =1) self.playing_tune.grid(row =2,column =0) def tracklist_widget(self): self.listtitle =Label(self.tracklist,font =("times new roman",15,"bold"),bg="white", fg ="dodgerblue") self.listtitle['text'] =f"Playlist: {len(self.playlist)} " self.listtitle.configure(width =10, height =1) self.listtitle.grid(row =0,column =0) self.scrollbar =Scrollbar(self.tracklist,orient =VERTICAL) self.scrollbar.grid(row=0, column=1,rowspan =10, sticky="NS") self.list =Listbox(self.tracklist, selectmode =SINGLE, yscrollcommand =self.scrollbar.set, selectbackground ="sky blue") self.track_listing() self.list.config(height =22) self.list.bind('<Double-1>', self.play_track) self.scrollbar.config(command =self.list.yview) self.list.grid(row=2, column=0, rowspan =5) def track_listing(self): for index, track in enumerate(self.playlist): self.list.insert(index,os.path.basename(track)) def controls_widget(self): self.tracklister =Button(self.controls, font =("times new roman",15,"bold"), bg="white", fg ="dodgerblue", padx =10,pady =5, command =self.select_track ) self.tracklister['text'] ="Load tracks" self.tracklister.grid(row =0,column =0,padx=10,pady =5) self.prevbutton =Button(self.controls,image =prev_icon, command =self.prev_track) self.prevbutton.grid(row =0,column =1,pady =5) self.pausebutton =Button(self.controls,image =pauseicon, command =self.pause_track) self.pausebutton.grid(row =0,column =2,pady =5) self.nextbutton =Button(self.controls,image =next_icon, command =self.next_track) self.nextbutton.grid(row =0,column =3,pady =5) self.volume =DoubleVar() self.slider =Scale(self.controls, from_ =0, to =10, orient =HORIZONTAL) self.slider['variable'] =self.volume self.slider['command'] =self.change_volume self.slider.set(3) mixer.music.set_volume(0.3) self.slider.grid(row =0, column=4,padx=10,pady =5) def change_volume(self, event =None): self.v =self.volume.get() mixer.music.set_volume(self.v/10) def select_track(self): self.tunes =[] directory =filedialog.askdirectory() for rooot, dirs, files in os.walk(directory): for file in files: if os.path.splitext(file)[1] =='.mp3': path =(rooot + '/' + file).replace('\\','/') self.tunes.append(path) with open("tracks.pickle", "wb") as f: pickle.dump(self.tunes, f) self.playlist =self.tunes self.listtitle['text'] =f"Playlist: {len(self.playlist)}" self.list.delete(0, END) self.track_listing() def play_track(self, event =None): try: if event is not None: self.track_index =self.list.curselection()[0] for i in range(len(self.playlist)): self.list.itemconfigure(i,bg="white",fg="black") mixer.music.load(self.playlist[self.track_index]) self.track_paused =False self.track_played =True self.pausebutton['image'] =playicon self.playing_tune.anchor('w') self.playing_tune['text'] =os.path.basename(self.playlist[self.track_index]) self.list.activate(self.track_index) self.list.itemconfigure(self.track_index,bg="teal", fg="white") mixer.music.play() except Exception as e: pass def pause_track(self): if not self.track_paused: try: self.track_paused =True self.pausebutton['image'] =pauseicon mixer.music.pause() except Exception as e: pass else: try: if not self.track_played: self.play_track() self.track_paused =False self.pausebutton['image'] =playicon mixer.music.unpause() except Exception as e: pass def next_track(self): if self.track_index > len(self.playlist) -1: self.track_index =0 self.track_index +=1 self.play_track() def prev_track(self): if self.track_index < 0: self.track_index =len(self.playlist) -1 self.track_index -=1 self.play_track()
class ShiftApp(): ### In-progress Shift application ### def __init__(self, db, job_name): self.db = db self.events = {} self.job_name = job_name self.start_time = time.time() self.break_start = 0 self.break_time = 0 self.id = self.db.add_shift(self.job_name, self.start_time, None, self.break_time, None) self.tasks = {} self.task_index = [] self.cur_task = 0 self.root = Tk() self.root.title(f'Shift - {job_name}') self.root.resizable(False, False) self.root.overrideredirect(0) self.tm1_selection = StringVar(self.root) self.tm2_selection = StringVar(self.root) self.container = ttk.Frame(self.root) self.frame = ttk.Frame(self.container) self.job_label = ttk.Label(self.frame, text=f'Job: {job_name}') self.elapsed_time_label = ttk.Label(self.frame, width=18) self.task_frame = ttk.Frame(self.frame) self.task_label = ttk.Label(self.task_frame, text="Tasks:") self.tm1_options, self.tm2_options = ('All Job Tasks', 'Shift Tasks Only'), () self.task_menu1 = ttk.OptionMenu(self.frame, self.tm1_selection, self.tm1_options[0], *self.tm1_options) self.task_entry = ttk.Entry(self.frame) self.task_list = Listbox(self.task_frame, selectmode=SINGLE, width=20, height=12, relief='sunken') vbar = ttk.Scrollbar(self.task_frame, orient='vertical', command=self.task_list.yview) self.task_list.config(yscrollcommand=vbar.set) self.new_task_button = ttk.Button(self.frame, text="New Task", command=self.new_task) self.button_frame = ttk.Frame(self.frame) self.pause_button = ttk.Button(self.button_frame, text='Pause', command=self.toggle_break, width=7) # TODO: Add info button self.cancel_button = ttk.Button(self.frame, text='Cancel', command=self.cancel_prompt) self.notes = ScrolledText(self.frame, undo=True, width=60, height=15, relief='sunken') self.report_job_button = ttk.Button(self.frame, text='Prior Shifts', command=self.launch_report_edit) self.save_button = ttk.Button(self.frame, text='Stop and Save', command=self.end_prompt) self.job_label.grid(column=2, columnspan=2, row=1, sticky=W, pady=5) self.elapsed_time_label.grid(column=4, row=1, sticky=E) self.task_menu1.grid(column=1, row=1, sticky=(E, W), padx=(0, 15)) self.task_entry.grid(column=1, row=2, sticky=(E, W), padx=(0, 15)) self.task_list.grid(column=1, row=1, sticky=(N, S)) vbar.grid(column=2, row=1, sticky=(N, S)) self.task_frame.grid(column=1, row=3, sticky=(N, S), pady=(5, 5)) self.new_task_button.grid(column=1, row=4, sticky=W) self.button_frame.grid(column=2, columnspan=2, row=2, sticky=W) self.pause_button.grid(column=2, row=1, sticky=W) self.cancel_button.grid(column=4, row=2, sticky=E) self.notes.grid(column=2, columnspan=3, row=3, pady=(5, 5)) self.report_job_button.grid(column=2, row=4, sticky=W) self.save_button.grid(column=4, row=4, sticky=E) self.frame.grid(column=0, row=0, padx=5, pady=5) self.container.grid(column=0, row=0) self.root.protocol("WM_DELETE_WINDOW", self.cancel_prompt) self.task_menu1.bind("<ButtonRelease-1>", self.filter_tasks) # filter_tasks clears notes self.task_entry.bind("<KeyRelease>", self.filter_tasks) self.task_list.bind("<ButtonRelease-1>", self.focus_task) self.task_list.bind("<Double-Button-1>", self.copy_task_title) self.notes.bind("<Command-Z>", self.edit_undo) self.time_counter() self.auto_save() self.get_tasks() def edit_undo(self, event=None): try: self.notes.edit_undo() except TclError as e: print(e) def get_tasks(self, event=None, **kwargs): ## Gets set of filtered tasks from db ## if "Job" in self.tm1_selection.get(): kwargs["job_name"] = self.job_name else: kwargs["shift_id"] = self.id tasks = self.db.report_tasks(**kwargs) self.tasks.clear() for task in tasks: self.tasks[task["id"]] = task self.task_list.delete(0, END) self.cur_task = 0 self.task_index = [] self.task_list.insert(END, "Shift Notes") self.task_index.append(None) for _, task in self.tasks.items(): self.task_list.insert(END, task['title']) self.task_index.append(task["id"]) def filter_tasks(self, event=None): # TODO: set focus to 'Shift Notes' search_term = self.task_entry.get() if search_term in ('', ' '): search_term = None self.get_tasks(search_term=search_term) notes = self.db.get_shift(self.id)["notes"] self.notes.delete("0.0", END) self.notes.insert(END, notes) def new_task(self): task_title = self.task_entry.get() if task_title not in ('', ' '): task = self.db.add_task(self.id, self.job_name, task_title) self.task_entry.delete(0, END) self.get_tasks() self.task_list.activate(self.task_index.index(task["id"])) def focus_task( self, event=None): ## displays notes for the currently selected task ## if self.task_list.curselection(): new_cur = self.task_list.curselection()[0] if new_cur == self.cur_task: return notes = self.notes.get(0.0, END).strip('\n') if self.cur_task: # if the notes being displayed belong to a task task_id = self.task_index[self.cur_task] self.tasks[task_id]["notes"] = notes else: self.db.update_shift(self.id, notes=notes) if new_cur: # if the new notes to be displayed belong to a task task_id = self.task_index[new_cur] notes = self.tasks[task_id]["notes"] else: shift = self.db.report_shifts(shift_id=self.id)[0] notes = shift["notes"] self.notes.delete('0.0', END) self.notes.insert(END, notes) self.cur_task = new_cur def copy_task_title( self, event=None ): ## Copies title from current selection in task_list to task_entry ## cur = self.task_list.curselection()[0] if cur: task = self.tasks[self.task_index[cur]] self.task_entry.delete(0, END) self.task_entry.insert(0, task["title"]) self.filter_tasks() def time_counter(self): if not self.break_start: elapsed_time = time.gmtime(time.time() - self.start_time - self.break_time) elapsed_hours = time.strftime('%H', elapsed_time) elapsed_minutes = time.strftime('%M', elapsed_time) elapsed_seconds = time.strftime('%S', elapsed_time) self.elapsed_time_label[ 'text'] = f'Elapsed Time: {elapsed_hours}:{elapsed_minutes}:{elapsed_seconds}' self.events['time_counter'] = self.root.after(200, self.time_counter) def auto_save(self): self.save_update() self.events['auto_save'] = self.root.after(5000, self.auto_save) def save_update( self): ## Commits current state for shift and tasks to db ## if self.break_start: break_time = self.break_time + (time.time() - self.break_start) else: break_time = self.break_time end_time = time.time() notes = self.notes.get(0.0, END).strip('\n') if self.cur_task: # if the notes being displayed belong to a task task_id = self.task_index[self.cur_task] self.tasks[task_id]["notes"] = notes notes = None self.db.update_shift(self.id, end_time=end_time, break_time=break_time, notes=notes) for _, task in self.tasks.items(): self.db.update_task(**task) def toggle_break(self): if not self.break_start: self.break_start = time.time() self.job_label['text'] = f'{self.job_name} - Paused' self.pause_button['text'] = 'Resume' else: self.break_time += time.time() - self.break_start self.break_start = 0 self.job_label['text'] = self.job_name self.pause_button['text'] = 'Pause' def end_prompt(self): popup = PopConfirm("Save and exit shift?", self.end_shift) popup.root.mainloop() def cancel_prompt(self): popup = PopConfirm("Cancel and delete shift?", self.cancel_shift) popup.root.mainloop() def end_shift(self): self.save_update() self.db.complete_shift(self.id) self.close() def cancel_shift(self): self.db.remove_shift(self.id) self.close() def launch_report_edit(self): self.report_edit_window = ReportEditApp(self.db, job_name=self.job_name) self.report_edit_window.root.mainloop() def close(self): for val in self.events.values(): self.root.after_cancel(val) self.root.destroy()
class SettingsFrame(Frame): ''' Frame inheritance class for application settings and controls. ''' def __init__(self, app, *args, **kwargs): ''' Constructor. ''' self.__app = app # Reference to main application class self.__master = self.__app.get_master() # Reference to root class (Tk) Frame.__init__(self, self.__master, *args, **kwargs) self._show_advanced = False self._settings = {} self._ports = () self._port = StringVar() self._port_desc = StringVar() self._baudrate = IntVar() self._databits = IntVar() self._stopbits = DoubleVar() self._parity = StringVar() self._rtscts = IntVar() self._xonxoff = IntVar() self._protocol = IntVar() self._raw = IntVar() self._autoscroll = IntVar() self._maxlines = IntVar() self._webmap = IntVar() self._mapzoom = IntVar() self._units = StringVar() self._format = StringVar() self._datalog = IntVar() self._record_track = IntVar() self._noports = True self._validsettings = True self._logpath = None self._trackpath = None self._img_conn = ImageTk.PhotoImage(Image.open(ICON_CONN)) self._img_disconn = ImageTk.PhotoImage(Image.open(ICON_DISCONN)) self._img_ubxconfig = ImageTk.PhotoImage(Image.open(ICON_UBXCONFIG)) self._img_dataread = ImageTk.PhotoImage(Image.open(ICON_LOGREAD)) self._body() self._do_layout() self._get_ports() self._reset() def _body(self): ''' Set up frame and widgets. ''' for i in range(4): self.grid_columnconfigure(i, weight=1) self.grid_rowconfigure(0, weight=1) self.option_add("*Font", self.__app.font_sm) # Serial port settings self._frm_basic = Frame(self) self._lbl_port = Label(self._frm_basic, text="Port") self._lbx_port = Listbox(self._frm_basic, border=2, relief="sunken", bg=ENTCOL, width=28, height=5, justify=LEFT, exportselection=False) self._scr_portv = Scrollbar(self._frm_basic, orient=VERTICAL) self._scr_porth = Scrollbar(self._frm_basic, orient=HORIZONTAL) self._lbx_port.config(yscrollcommand=self._scr_portv.set) self._lbx_port.config(xscrollcommand=self._scr_porth.set) self._scr_portv.config(command=self._lbx_port.yview) self._scr_porth.config(command=self._lbx_port.xview) self._lbx_port.bind("<<ListboxSelect>>", self._on_select_port) self._lbl_baudrate = Label(self._frm_basic, text="Baud rate") self._spn_baudrate = Spinbox(self._frm_basic, values=(BAUDRATES), width=8, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._baudrate) self._btn_toggle = Button(self._frm_basic, text=ADVOFF, width=3, command=self._toggle_advanced) self._frm_advanced = Frame(self) self._lbl_databits = Label(self._frm_advanced, text="Data Bits") self._spn_databits = Spinbox(self._frm_advanced, values=(8, 7, 6, 5), width=3, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._databits) self._lbl_stopbits = Label(self._frm_advanced, text="Stop Bits") self._spn_stopbits = Spinbox(self._frm_advanced, values=(2, 1.5, 1), width=3, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._stopbits) self._lbl_parity = Label(self._frm_advanced, text="Parity") self._spn_parity = Spinbox(self._frm_advanced, values=("None", "Even", "Odd", "Mark", "Space"), width=6, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._parity) self._chk_rts = Checkbutton(self._frm_advanced, text="RTS/CTS", variable=self._rtscts) self._chk_xon = Checkbutton(self._frm_advanced, text="Xon/Xoff", variable=self._xonxoff) self._frm_buttons = Frame(self) self._btn_connect = Button(self._frm_buttons, width=45, height=35, image=self._img_conn, command=lambda: self.__app.serial_handler.connect()) self._btn_disconnect = Button(self._frm_buttons, width=45, height=35, image=self._img_disconn, command=lambda: self.__app.serial_handler.disconnect(), state=DISABLED) self._btn_connect_file = Button(self._frm_buttons, width=45, height=35, image=self._img_dataread, command=lambda: self._on_data_stream()) self._lbl_status_preset = Label(self._frm_buttons, font=self.__app.font_md2, text='') # Other configuration options self._frm_options = Frame(self) self._lbl_protocol = Label(self._frm_options, text=LBLPROTDISP) self._rad_nmea = Radiobutton(self._frm_options, text="NMEA", variable=self._protocol, value=NMEA_PROTOCOL) self._rad_ubx = Radiobutton(self._frm_options, text="UBX", variable=self._protocol, value=UBX_PROTOCOL) self._rad_all = Radiobutton(self._frm_options, text="ALL", variable=self._protocol, value=MIXED_PROTOCOL) self._lbl_consoledisplay = Label(self._frm_options, text=LBLDATADISP) self._rad_parsed = Radiobutton(self._frm_options, text="Parsed", variable=self._raw, value=0) self._rad_raw = Radiobutton(self._frm_options, text="Raw", variable=self._raw, value=1) self._lbl_format = Label(self._frm_options, text="Degrees Format") self._spn_format = Spinbox(self._frm_options, values=(DDD, DMS, DMM), width=6, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._format) self._lbl_units = Label(self._frm_options, text="Units") self._spn_units = Spinbox(self._frm_options, values=(UMM, UIK, UI, UMK), width=13, state=READONLY, readonlybackground=ENTCOL, wrap=True, textvariable=self._units) self._chk_scroll = Checkbutton(self._frm_options, text="Autoscroll", variable=self._autoscroll) self._spn_maxlines = Spinbox(self._frm_options, values=("100", "200", "500", "1000", "2000"), width=6, readonlybackground=ENTCOL, wrap=True, textvariable=self._maxlines, state=READONLY) self._chk_webmap = Checkbutton(self._frm_options, text="Web Map Zoom", variable=self._webmap) self._scl_mapzoom = Scale(self._frm_options, from_=1, to=20, orient=HORIZONTAL, relief="sunken", bg=ENTCOL, variable=self._mapzoom) self._chk_datalog = Checkbutton(self._frm_options, text=LBLDATALOG, variable=self._datalog, command=lambda: self._on_data_log()) self._chk_recordtrack = Checkbutton(self._frm_options, text=LBLTRACKRECORD, variable=self._record_track, command=lambda: self._on_record_track()) self._lbl_ubxconfig = Label(self._frm_options, text=LBLUBXCONFIG) self._btn_ubxconfig = Button(self._frm_options, width=45, height=35, text='UBX', image=self._img_ubxconfig, command=lambda: self._on_ubx_config(), state=DISABLED) def _do_layout(self): ''' Position widgets in frame. ''' self._frm_basic.grid(column=0, row=0, columnspan=4, sticky=(W, E)) self._lbl_port.grid(column=0, row=0, sticky=(W)) self._lbx_port.grid(column=1, row=0, sticky=(W, E), padx=3, pady=3) self._scr_portv.grid(column=2, row=0, sticky=(N, S)) self._scr_porth.grid(column=1, row=1, sticky=(E, W)) self._lbl_baudrate.grid(column=0, row=2, sticky=(W)) self._spn_baudrate.grid(column=1, row=2, sticky=(W), padx=3, pady=3) self._btn_toggle.grid(column=2, row=2, sticky=(E)) self._frm_advanced.grid_forget() self._lbl_databits.grid(column=0, row=0, sticky=(W)) self._spn_databits.grid(column=1, row=0, sticky=(W), padx=3, pady=3) self._lbl_stopbits.grid(column=2, row=0, sticky=(W)) self._spn_stopbits.grid(column=3, row=0, sticky=(W), padx=3, pady=3) self._lbl_parity.grid(column=0, row=1, sticky=(W)) self._spn_parity.grid(column=1, row=1, sticky=(W), padx=3, pady=3) self._chk_rts.grid(column=2, row=1, sticky=(W)) self._chk_xon.grid(column=3, row=1, sticky=(W), padx=3, pady=3) ttk.Separator(self).grid(column=0, row=2, columnspan=4, padx=3, pady=3, sticky=(W, E)) self._frm_buttons.grid(column=0, row=3, columnspan=4, sticky=(W, E)) self._btn_connect.grid(column=0, row=0, padx=3, pady=3) self._btn_connect_file.grid(column=1, row=0, padx=3, pady=3) self._btn_disconnect.grid(column=3, row=0, padx=3, pady=3) ttk.Separator(self).grid(column=0, row=7, columnspan=4, padx=3, pady=3, sticky=(W, E)) self._frm_options.grid(column=0, row=8, columnspan=4, sticky=(W, E)) self._lbl_protocol.grid(column=0, row=0, padx=3, pady=3, sticky=(W)) self._rad_nmea.grid(column=1, row=0, padx=0, pady=0, sticky=(W)) self._rad_ubx.grid(column=2, row=0, padx=0, pady=0, sticky=(W)) self._rad_all.grid(column=3, row=0, padx=0, pady=0, sticky=(W)) self._lbl_consoledisplay.grid(column=0, row=1, padx=2, pady=3, sticky=(W)) self._rad_parsed.grid(column=1, row=1, padx=1, pady=3, sticky=(W)) self._rad_raw.grid(column=2, row=1, padx=2, pady=3, sticky=(W)) self._lbl_format.grid(column=0, row=2, padx=3, pady=3, sticky=(W)) self._spn_format.grid(column=1, row=2, padx=2, pady=3, sticky=(W)) self._lbl_units.grid(column=0, row=3, padx=3, pady=3, sticky=(W)) self._spn_units.grid(column=1, row=3, columnspan=3, padx=2, pady=3, sticky=(W)) self._chk_scroll.grid(column=0, row=4, padx=3, pady=3, sticky=(W)) self._spn_maxlines.grid(column=1, row=4, columnspan=3, padx=3, pady=3, sticky=(W)) self._chk_webmap.grid(column=0, row=5, sticky=(W)) self._scl_mapzoom.grid(column=1, row=5, columnspan=3, sticky=(W)) self._chk_datalog.grid(column=0, row=6, padx=3, pady=3, sticky=(W)) self._chk_recordtrack.grid(column=0, row=7, padx=3, pady=3, sticky=(W)) ttk.Separator(self._frm_options).grid(column=0, row=8, columnspan=4, padx=3, pady=3, sticky=(W, E)) self._lbl_ubxconfig.grid(column=0, row=9, padx=3, pady=3, sticky=(W)) self._btn_ubxconfig.grid(column=1, row=9, padx=3, pady=3, sticky=(W)) def _on_select_port(self, *args, **kwargs): ''' Get selected port from listbox and set global variable. ''' idx = self._lbx_port.curselection() if idx == "": idx = 0 port_orig = self._lbx_port.get(idx) port = port_orig[0:port_orig.find(":")] desc = port_orig[port_orig.find(":") + 1:] if desc == '': desc = "device" self._port.set(port) self._port_desc.set(desc) def _on_ubx_config(self, *args, **kwargs): ''' Open UBX configuration dialog panel. ''' self.__app.ubxconfig() def _on_data_log(self): ''' Start or stop data logger ''' if self._datalog.get() == 1: self._logpath = self.__app.file_handler.set_logfile_path() if self._logpath is not None: self.__app.set_status("Data logging enabled: " + self._logpath, "green") else: self._datalog.set(False) else: self._logpath = None self._datalog.set(False) # self.__app.file_handler.close_logfile() self.__app.set_status("Data logging disabled", "blue") def _on_record_track(self): ''' Start or stop track recorder ''' if self._record_track.get() == 1: self._trackpath = self.__app.file_handler.set_trackfile_path() if self._trackpath is not None: self.__app.set_status("Track recording enabled: " + self._trackpath, "green") else: self._record_track.set(False) else: self._trackpath = None self._record_track.set(False) # self.__app.file_handler.close_trackfile() self.__app.set_status("Track recording disabled", "blue") def _on_data_stream(self): ''' Start data file streamer ''' self._logpath = self.__app.file_handler.open_logfile_input() if self._logpath is not None: self.__app.set_status("") self.__app.serial_handler.connect_file() def _toggle_advanced(self): ''' Toggle advanced serial port settings panel on or off ''' self._show_advanced = not self._show_advanced if self._show_advanced: self._frm_advanced.grid(column=0, row=1, columnspan=3, sticky=(W, E)) self._btn_toggle.config(text=ADVON) else: self._frm_advanced.grid_forget() self._btn_toggle.config(text=ADVOFF) def _get_ports(self): ''' Populate list of available serial ports using pyserial comports tool. If no ports found, disable all connection-dependent widgets. Attempt to preselect the first port that has a recognisable GPS designation in its description (usually only works on Posix platforms - Windows doesn't parse UART device desc or HWID) ''' self._ports = sorted(comports()) init_idx = 0 port = '' desc = '' if len(self._ports) > 0: for idx, (port, desc, _) in enumerate(self._ports, 1): self._lbx_port.insert(idx, port + ": " + desc) for kgp in KNOWNGPS: if kgp in desc: init_idx = idx break self._noports = False else: self._noports = True self.set_controls(NOPORTS) self._lbx_port.activate(init_idx) self._port.set(port) self._port_desc.set(desc) def _reset(self): ''' Reset settings to defaults. ''' self._baudrate.set(BAUDRATES[4]) # 9600 self._databits.set(8) self._stopbits.set(1) self._parity.set("None") self._rtscts.set(False) self._xonxoff.set(False) self._protocol.set(MIXED_PROTOCOL) self._format.set(DDD) self._units.set(UMM) self._autoscroll.set(1) self._maxlines.set(300) self._raw.set(False) self._webmap.set(False) self._mapzoom.set(10) self._datalog.set(False) self._record_track.set(False) def set_controls(self, status): ''' ...for the heart of the sun. Public method to enable and disable serial port controls depending on connection status. ''' self._lbl_port.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._lbx_port.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._lbl_baudrate.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._spn_baudrate.configure(state=(READONLY if status == DISCONNECTED else DISABLED)) self._lbl_databits.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._spn_databits.configure(state=(READONLY if status == DISCONNECTED else DISABLED)) self._lbl_stopbits.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._spn_stopbits.configure(state=(READONLY if status == DISCONNECTED else DISABLED)) self._lbl_parity.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._spn_parity.configure(state=(READONLY if status == DISCONNECTED else DISABLED)) self._chk_rts.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._chk_xon.configure(state=(NORMAL if status == DISCONNECTED else DISABLED)) self._btn_connect.config(state=(DISABLED if status in \ (CONNECTED, CONNECTED_FILE, NOPORTS) \ else NORMAL)) self._btn_disconnect.config(state=(DISABLED if status in \ (DISCONNECTED, NOPORTS) else NORMAL)) self._chk_datalog.config(state=(DISABLED if status in \ (CONNECTED, CONNECTED_FILE, NOPORTS) \ else NORMAL)) self._chk_recordtrack.config(state=(DISABLED if status in \ (CONNECTED, CONNECTED_FILE) \ else NORMAL)) self._btn_connect_file.config(state=(DISABLED if status in \ (CONNECTED, CONNECTED_FILE) \ else NORMAL)) self._btn_ubxconfig.config(state=(DISABLED if status in \ (DISCONNECTED, CONNECTED_FILE, NOPORTS) \ else NORMAL)) self.__app.menu.options_menu.entryconfig(0, state=(DISABLED if status in \ (CONNECTED_FILE, DISCONNECTED, NOPORTS) \ else NORMAL)) def get_settings(self): ''' Public method returns all settings as a dict. ''' self._settings['port'] = self._port.get() self._settings['noports'] = self._noports self._settings['port_desc'] = self._port_desc.get() self._settings['baudrate'] = self._baudrate.get() self._settings['databits'] = self._databits.get() self._settings['stopbits'] = self._stopbits.get() self._settings['parity'] = self._parity.get() self._settings['rtscts'] = self._rtscts.get() self._settings['xonxoff'] = self._xonxoff.get() self._settings['protocol'] = self._protocol.get() self._settings['raw'] = self._raw.get() self._settings['autoscroll'] = self._autoscroll.get() self._settings['maxlines'] = self._maxlines.get() self._settings['webmap'] = self._webmap.get() self._settings['mapzoom'] = self._mapzoom.get() self._settings['units'] = self._units.get() self._settings['format'] = self._format.get() self._settings['logpath'] = self._logpath self._settings['datalogging'] = self._datalog.get() self._settings['recordtrack'] = self._record_track.get() return self._settings def get_size(self): ''' Get current frame size. ''' self.update_idletasks() # Make sure we know about any resizing return (self.winfo_width(), self.winfo_height())
class SettingsForm: """TODO: INSERT DOCSTRING.""" def __init__(self): """TODO: INSERT DOCSTRING.""" self.form = tk.Tk() self.form.geometry(INIT_SCREEN_SIZE) self.form.minsize(MIN_SCREEN_SIZE[0], MIN_SCREEN_SIZE[1]) self.form.title(SCREEN_TITLE) # Store the last status of the directory path to determine if the # directory changed in order to update the file display boxes. self.last_dir_path: str = Settings.directory_path # Each file in the current directory is stored as the key of a # dictionary, with each key mapping to a bool of if that file # is enabled or not. self.all_files: Dict[str, bool] = {} self.load_all_files() # Create the main frame to hold all the elements. self.frame: Frame = Frame(self.form, background=LIGHT_GRAY) # Make the main frame fit the size of the window. self.frame.pack(fill=BOTH, expand=1) # Track the most recenly selected item in the active files and disabled # files listboxes. self.sel_item_in_active_files: int = 0 self.sel_item_in_disabled_files: int = 0 self.create_widgets() def create_widgets(self): """Create all form elements, and populate the form. This should only be called once when the form is being created. """ # # Form Layout Hierarchy: # # Main Frame # |---File Frame # | |---Active Files # | |---File Button Frame # | | |---Activate Button # | | |---Disable Button # | |---Disable Files # |---Content Frame # | |---Directory Frame # | | |---Directory Label # | | |---Directory Input # | |---Font Size Frame # | | |---Font Size Label # | | |---Font Size Input # | |---Typeface Frame # | | |---Typeface Label # | | |---Typeface Input # | |---Theme Frame # | | |---Theme Label # | | |---Theme Input # | |---Display Item Frame # | | |---Display Item Label # | | |---Display Item Input # | |---Button Frame # | | |---Save Button # | | |---Cancel Button # # ----- FILE SELECTOR ----- self.file_frame = Frame(self.frame, background=LIGHT_GRAY) self.file_frame.pack(fill=BOTH, expand=1, side=LEFT, padx=5, pady=5) self.active_files = Listbox(self.file_frame, relief=FLAT, foreground=BLACK, background=WHITE, highlightthickness=0) self.active_files.pack(fill=BOTH, expand=1, padx=5, pady=5) self.refresh_active_files() self.file_button_frame = Frame(self.file_frame, background=LIGHT_GRAY) self.file_button_frame.pack(fill=BOTH, expand=0, padx=5, pady=5) self.activate_button = Button(self.file_button_frame, text='▲', relief=FLAT, foreground=BLACK, background=MEDIUM_GRAY, command=self.activate_callback, highlightthickness=0) self.activate_button.pack(fill=BOTH, expand=1, side=LEFT, padx=5, pady=5) self.disable_button = Button(self.file_button_frame, text='▼', relief=FLAT, foreground=BLACK, background=MEDIUM_GRAY, command=self.disable_callback, highlightthickness=0) self.disable_button.pack(fill=BOTH, expand=1, side=RIGHT, padx=5, pady=5) self.disabled_files = Listbox(self.file_frame, relief=FLAT, foreground=BLACK, background=WHITE, highlightthickness=0) self.disabled_files.pack(fill=BOTH, expand=1, padx=5, pady=5) self.refresh_disabled_files() # ------------------------------ self.content_frame = Frame(self.frame, background=LIGHT_GRAY) self.content_frame.pack(fill=Y, expand=0, side=RIGHT, padx=5, pady=10) # ----- DIRECTORY SETTING ----- self.directory_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.directory_frame.pack(fill=X, expand=0, side=TOP, padx=5, pady=10) self.directory_label = Label(self.directory_frame, text='Directory: ', font=DEFAULT_FONT, background=LIGHT_GRAY, foreground=BLACK) self.directory_label.pack(side=LEFT) self.directory_input = Entry(self.directory_frame, text='null', font=DEFAULT_FONT, background=WHITE, relief=FLAT, width=DEFAULT_WIDTH, foreground=BLACK, highlightthickness=0) self.directory_input.pack(side=LEFT) self.directory_input.insert(0, Settings.directory_path) # ------------------------------ # ----- FONT SIZE SETTING ----- self.font_size_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.font_size_frame.pack(fill=X, expand=0, side=TOP, padx=5, pady=10) self.font_size_label = Label(self.font_size_frame, text='Font Size:', font=DEFAULT_FONT, background=LIGHT_GRAY, foreground=BLACK) self.font_size_label.pack(side=LEFT, anchor=SW) self.font_size_input = Scale(self.font_size_frame, orient=HORIZONTAL, font=DEFAULT_FONT, background=WHITE, relief=FLAT, from_=4, to=140, sliderlength=20, resolution=4, length=175, foreground=BLACK, highlightthickness=0) self.font_size_input.pack(side=LEFT) self.font_size_input.set(Settings.font_size) # ------------------------------ # ----- TYPEFACE SETTING ----- self.typeface_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.typeface_frame.pack(fill=X, expand=0, side=TOP, padx=5, pady=10) self.typeface_label = Label(self.typeface_frame, text='Typeface: ', font=DEFAULT_FONT, background=LIGHT_GRAY, foreground=BLACK) self.typeface_label.pack(side=LEFT) self.typeface_input = Entry(self.typeface_frame, text='Verdana', font=DEFAULT_FONT, width=DEFAULT_WIDTH, background=WHITE, relief=FLAT, foreground=BLACK, highlightthickness=0) self.typeface_input.pack(side=LEFT) self.typeface_input.insert(0, Settings.typeface) # ------------------------------ # ----- THEME SETTING ----- self.theme_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.theme_frame.pack(fill=X, expand=0, side=TOP, padx=5, pady=10) self.theme_label = Label(self.theme_frame, text='Theme: ', font=DEFAULT_FONT, background=LIGHT_GRAY, foreground=BLACK) self.theme_label.pack(side=LEFT) self.theme_input = Listbox(self.theme_frame, font=DEFAULT_FONT, background=WHITE, relief=FLAT, height=len(Settings.theme_names) + 3, width=DEFAULT_WIDTH + 2, foreground=BLACK, highlightthickness=0) self.theme_input.pack(side=LEFT) self.theme_input.insert( END, CURRENT_THEME_STR.format(Settings.current_theme)) self.theme_input.insert(END, '') for theme_name in Settings.theme_names: self.theme_input.insert(END, theme_name) self.theme_input.select_set(0) # ------------------------------ # ----- ITEM RANGE ----- self.display_item_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.display_item_frame.pack(fill=X, expand=0, side=TOP, padx=5, pady=10) self.display_item_label = Label(self.display_item_frame, text='Display Item: ', font=DEFAULT_FONT, background=LIGHT_GRAY, foreground=BLACK) self.display_item_label.pack(side=LEFT) self.display_item_input = Spinbox(self.display_item_frame, from_=-1, to=100, font=DEFAULT_FONT, width=DEFAULT_WIDTH - 4, background=WHITE, relief=FLAT, foreground=BLACK, highlightthickness=0) self.display_item_input.pack(side=LEFT) self.display_item_input.delete(0, END) self.display_item_input.insert(0, Settings.display_item) # ------------------------------ # ----- SAVE AND CANCEL BUTTONS ----- self.button_frame = Frame(self.content_frame, background=LIGHT_GRAY) self.button_frame.pack(fill=X, expand=0, side=BOTTOM, padx=10, pady=10) self.save_button = Button(self.button_frame, text='Save & Quit', background=MEDIUM_GRAY, relief=FLAT, command=self.save_callback, foreground=BLACK, highlightthickness=0) self.save_button.pack(side=RIGHT, padx=3, pady=3) self.cancel_button = Button(self.button_frame, text='Cancel', background=MEDIUM_GRAY, relief=FLAT, command=self.cancel_callback, foreground=BLACK, highlightthickness=0) self.cancel_button.pack(side=LEFT, padx=3, pady=3) # ------------------------------ def cancel_callback(self) -> None: """Restore the previous saved settings and close the form.""" FileReader.load_settings() self.form.destroy() def save_callback(self) -> None: """Save the newly updated settings and close the form.""" FileReader.save_settings() self.form.destroy() def activate_callback(self) -> None: """Take currently selected 'disabled' file and sets it to 'enabled'.""" # Get and save the index of the currently selected item if it exists, # this allows for the next item to be automatically selected after the # currently selected item is transfered, allowing for rapid item # activation. current_selection: tuple = self.disabled_files.curselection() if len(current_selection) > 0: self.sel_item_in_disabled_files = current_selection[0] file: str = str(self.disabled_files.get(ACTIVE)) if file in self.all_files.keys(): self.all_files[file] = True # Update the file display text boxes. self.refresh_active_files() self.refresh_disabled_files() self.disabled_files.activate(self.sel_item_in_disabled_files) def disable_callback(self) -> None: """Take currently selected 'enabled' file and sets it to 'disabled'.""" # Get and save the index of the currently selected item if it exists, # this allows for the next item to be automatically selected after the # currently selected item is transfered, allowing for rapid item # deactivation. current_selection: tuple = self.active_files.curselection() if len(current_selection) > 0: self.sel_item_in_active_files = current_selection[0] file: str = str(self.active_files.get(ACTIVE)) if file in self.all_files.keys(): self.all_files[file] = False # Update the file display text boxes. self.refresh_active_files() self.refresh_disabled_files() self.active_files.activate(self.sel_item_in_active_files) def refresh_active_files(self) -> None: """Clear and fill active files text box with current active files. This refreshes the data in the text box. Replacing the old data with the new data, keeping it up to date. """ self.active_files.delete(0, END) self.active_files.insert(END, ' active ') self.active_files.insert(END, '--------------------') for file in self.all_files: if self.all_files[file]: # File is active. self.active_files.insert(END, file) def refresh_disabled_files(self) -> None: """Clear and fill disabled files text box with current disabled files. This refreshes the data in the text box. Replacing the old data with the new data, keeping it up to date. """ self.disabled_files.delete(0, END) self.disabled_files.insert(END, ' disabled ') self.disabled_files.insert(END, '--------------------') for file in self.all_files: if not self.all_files[file]: # File is disabled. self.disabled_files.insert(END, file) def load_all_files(self) -> None: """Load all files from the current directory into self.all_files. This also sets the state if the files are active or disabled. """ dir_path: str = Settings.directory_path all_files: Dict[str, bool] = {} if os.path.exists(dir_path): # Loops through all files in the current directory. for file in [ f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f)) ]: is_active: bool = file in Settings.active_files # Add new file (key) and activity status (value) to all_files # dict. all_files[file] = is_active self.all_files = all_files def update_directory_path(self) -> str: """Return the current directory in the directory input box. Update the current working files and file display boxes if a new directory was set. Returns: str: The directory path to be saved to the settings. """ new_dir_path: str = self.directory_input.get() # Update the current working files and file display boxes if the # directory changed. This is done to always display the files that # are in the current directory. if self.last_dir_path != Settings.directory_path: self.load_all_files() self.refresh_active_files() self.refresh_disabled_files() self.last_dir_path = Settings.directory_path # Set the last directory, then return the new directory to be # set the the settings. This makes it so that the elements update # from the conditional above. This is because the Settings will be # updated after this function returns. elif new_dir_path != Settings.directory_path: self.last_dir_path = Settings.directory_path return new_dir_path return Settings.directory_path def update_theme(self) -> str: """Return the current theme in the theme input box. Use current theme if new theme isn't valid (i.e. title portion), and update the currenly selected theme in the input box. Returns: str: The name of the new theme to be saved to the settings. """ # Get the current selected theme from the Listbox. If the theme # is not valid, set it to the current theme so that a valid theme # is always used. theme_name: str = str(self.theme_input.get(ACTIVE)) if theme_name not in Settings.theme_names: theme_name = Settings.current_theme # Reset the first element of the Listbox which displays the current # theme, this shows the user the name of the theme that is currently # enabled. This also has the side effect of that element not being # able to be selected (which doesn't change anything). self.theme_input.delete(0) self.theme_input.insert( 0, CURRENT_THEME_STR.format(Settings.current_theme)) return theme_name def update_settings(self) -> None: """Set all of the current form values to the settings.""" # Get the current directory path. dir_path: str = self.update_directory_path() # Get the current theme name. theme_name: str = self.update_theme() # Get all active files. active_files: List[str] = [ f for f in self.all_files if self.all_files[f] ] # Get the current display item, or 0 if invalid value. display_item: int = 0 try: display_item = int(self.display_item_input.get()) except ValueError: pass # Update the setings to the new values. Settings.set_value({ "directory_path": dir_path, "active_files": active_files, "display_item": display_item, "font_size": self.font_size_input.get(), "typeface": self.typeface_input.get(), "current_theme": theme_name, }) def update(self): """Update the form screen. This is used to update the screen and settings every 'tick' when this function is called. """ try: self.update_settings() self.form.update_idletasks() self.form.update() except TclError: # The form has been destroyed (i.e. on exit) # Return None so the main form knows this form is no longer active. return None # Return itself so the main form knows this form is still active. return self
class TShapesWindow(Frame): """ Class represents auxiliary window containg shapes information. :param master: master window object. :type master: tkinter.Tk :param app: main app object. :type app: TApp """ def __init__(self, master, TApp): """ Call the parent class constructor and initialise object variables. """ super().__init__() self.TApp = TApp self.round_digits = TTicksSettings.ROUND_DIGITS self.init_widgets() def init_widgets(self): """ Init frame widgets. """ # Listbox self.shapes_list = Listbox(self, exportselection=False, selectmode=EXTENDED) self.grid_columnconfigure(0, weight=1, minsize=300) self.grid_rowconfigure(0, weight=1) self.shapes_list.grid(row=0, column=0, sticky=NSEW, padx=(5, 25), pady=5) self.shapes_list.bind("<<ListboxSelect>>", self.shapes_list_selected_item) # Listbox's yscrollbar self.shapes_list_scrollbar = Scrollbar(self, orient = VERTICAL, \ command = self.shapes_list.yview) self.shapes_list_scrollbar.grid(row=0, column=0, sticky=NS + E, padx=(0, 5), pady=5) self.shapes_list.config(yscrollcommand=self.shapes_list_scrollbar.set) # Drop down menu with materials self.material_box = Combobox(self, values=["pec", "free_space"]) self.material_box.grid(row=1, column=0, sticky=EW, padx=5, pady=5) self.material_box.bind("<<ComboboxSelected>>", self.assign_material_to_shape) # Right click popup menu self.init_popup_menu() self.shapes_list.bind("<Button-3>", self.show_popoup_menu) # Delete key removes selected shape(s) self.shapes_list.bind("<Delete>", self.remove_shape) def update_list(self, shapes, *, swap=False): """ Update shapes list. :param swap: shapes list selection swap toggle. :type swap: boolean """ selection = self.shapes_list.curselection() try: shape_num = selection[0] except: shape_num = 0 self.shapes_list.delete(0, END) coord_string = "" for i, single_shape in enumerate(shapes): if (single_shape.type == "Rectangle"): coord_string = "(" + str(round(single_shape.point1_mod.x, self.round_digits)) + \ ", " + str(round(single_shape.point1_mod.y, self.round_digits)) + \ "), (" + str(round (single_shape.point2_mod.x, self.round_digits)) + \ ", " + str(round(single_shape.point2_mod.y, self.round_digits)) + ")" elif (single_shape.type == "Cylinder"): coord_string = "(" + str(round(single_shape.centre_mod.x, self.round_digits)) + \ ", " + str(round(single_shape.centre_mod.y, self.round_digits)) + \ "), " + str(round(single_shape.radius_mod, self.round_digits)) elif (single_shape.type == "CylinSector"): coord_string = "(" + str(round(single_shape.centre_mod.x, self.round_digits)) + \ ", " + str(round(single_shape.centre_mod.y, self.round_digits)) + \ "), " + str(round(single_shape.radius_mod, self.round_digits)) + \ ", " + str(round(single_shape.start, self.round_digits)) + \ ", " + str(round(single_shape.extent, self.round_digits)) elif (single_shape.type == "Polygon"): coord_string = str(len(single_shape.points)) self.shapes_list.insert( i, str(i + 1) + ". " + single_shape.type + ": " + coord_string) coord_string = "" if (not swap): self.shapes_list.select_clear(0, END) if (shape_num >= self.shapes_list.size()): self.shapes_list.selection_set(shape_num - 1) self.shapes_list.activate(shape_num) else: for item in selection: self.shapes_list.selection_set(item) self.shapes_list.activate(shape_num) def assign_material_to_shape(self, event): """ Assign material to a shape. :param event: event evoking this method (listbox select). :type event: tkinter.Event """ material = self.material_box.get() try: shape_num = (self.shapes_list.curselection())[0] except: return else: if (shape_num < 0 or material == ""): return else: try: self.TApp.shapes[shape_num].material = material except Exception as message: messagebox.showerror("Material assignment error!", message) return def shapes_list_selected_item(self, event): """ Handle listbox selection event. :param event: event evoking this method (listbox select). :type event: tkinter.Event """ # Add multiple selection self.shapes_list.focus_force() try: shape_num = (self.shapes_list.curselection())[0] selection = self.shapes_list.curselection() except IndexError: return except Exception as message: messagebox.showerror("Error while picking shape!", message) if (shape_num < 0): return else: try: shape = self.TApp.shapes[shape_num] except Exception as message: messagebox.showerror("Materials list error", message) return self.material_box.set(str(shape.material)) for single_shape in self.TApp.shapes: single_shape.width = 1 for item in selection: self.TApp.shapes[item].width = 2 self.TApp.main_canvas.delete("all") for item in selection: self.shapes_list.selection_set(item) self.TApp.canvas_refresh() def init_popup_menu(self): """ Init shapes pane pup-up menu. """ self.popup_menu = Menu(self, tearoff=0) self.popup_menu.add_command(label="Edit shape", command=self.edit_shape) self.popup_menu.add_command(label="Change shape colour", command=self.change_shape_colour) self.popup_menu.add_command(label="Remove shape(s)", command=self.remove_shape) self.popup_menu.add_separator() self.popup_menu.add_command(label="Add vertex to polygon", command=self.add_vertex_to_polygon) self.popup_menu.add_separator() self.popup_menu.add_command(label="Copy shape", command=self.copy_shape) self.popup_menu.add_command(label="Paste shape", command=self.paste_shape) self.popup_menu.add_separator() self.popup_menu.add_command(label="Move up", command=self.move_shape_up) self.popup_menu.add_command(label="Move down", command=self.move_shape_down) self.popup_menu.add_command(label="Move to top", command=self.move_shape_top) self.popup_menu.add_command(label="Move to bottom", command=self.move_shape_bottom) def show_popoup_menu(self, event): """ Show shapes list pop-up menu. :param event: event evoking this method (RMB click). :type event: tkinter.Event """ try: self.popup_menu.post(event.x_root, event.y_root) finally: self.popup_menu.grab_release() def move_shape_up(self): """ Move a shape one place up the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: try: self.TApp.shapes.insert(shape_num - 1, self.TApp.shapes.pop(shape_num)) self.update_list(self.TApp.shapes) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh(swap=True) self.shapes_list.selection_set(shape_num - 1) self.shapes_list.activate(shape_num - 1) except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) return def move_shape_down(self): """ Move a shape one place down the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: try: self.TApp.shapes.insert(shape_num + 1, self.TApp.shapes.pop(shape_num)) self.update_list(self.TApp.shapes) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh(swap=True) self.shapes_list.selection_set(shape_num + 1) self.shapes_list.activate(shape_num + 1) except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) return def move_shape_top(self): """ Move a shape to the top of the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: try: self.TApp.shapes.insert(0, self.TApp.shapes.pop(shape_num)) self.update_list(self.TApp.shapes) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh(swap=True) self.shapes_list.selection_set(0) self.shapes_list.activate(0) # self.shapes_list.focus_set () except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) return def move_shape_bottom(self): """ Move a shape to the bottom of the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: try: self.TApp.shapes.append(self.TApp.shapes.pop(shape_num)) self.update_list(self.TApp.shapes) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh(swap=True) self.shapes_list.selection_set(END) self.shapes_list.activate(END) except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) return def edit_shape(self): """ Edit selected shape on the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: self.TApp.operations.append(TOperation("edit", shape = \ deepcopy(self.TApp.shapes[shape_num]), \ num = shape_num)) if (self.TApp.shapes[shape_num].type == "Rectangle"): self.TApp.edit_rectangle(shape_num) elif (self.TApp.shapes[shape_num].type == "Cylinder"): self.TApp.edit_cylin(shape_num) elif (self.TApp.shapes[shape_num].type == "CylinSector"): self.TApp.edit_cylin_sector(shape_num) elif (self.TApp.shapes[shape_num].type == "Polygon"): self.TApp.edit_polygon(shape_num) def change_shape_colour(self): """ Change selected shape on the shapes list colour. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return if (shape_num < 0): return else: self.TApp.change_shape_colour(shape_num=shape_num) def remove_shape(self, event=None): """ Remove selected shape on the shapes list. """ try: selection = self.shapes_list.curselection() except IndexError: return if (len(selection) == 0): return else: try: for item in reversed(selection): del self.TApp.shapes[item] self.update_list(self.TApp.shapes) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh() except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) return def add_vertex_to_polygon(self): """ Add a vertex to selected polygon on the shapes list. """ try: shape_num = (self.shapes_list.curselection())[0] except IndexError: return input_str = simpledialog.askstring("Input coordinates", "Give mew vertex's coordinates") point_mod_x, point_mod_y = [float(val) for val in input_str.split()] if (self.TApp.shapes[shape_num].type == "Polygon" and shape_num > -1): self.TApp.shapes[shape_num].add_vertex(x_mod=point_mod_x, y_mod=point_mod_y) self.TApp.main_canvas.delete("all") self.TApp.canvas_refresh() def copy_shape(self): """ Copy selected shape on the shapes list. """ try: shape_num = self.shapes_list.curselection()[0] except: shape_num = -1 if (shape_num > -1): try: self.TApp.copy_shape(shape_num=shape_num) except Exception as message: messagebox.showerror("Error while manipulating shapes list!", message) def paste_shape(self, *, deltax=15, deltay=15): """ Paste selected shape from buffer. :param deltax: pasted shape offset in x direction in pixels. :type deltax: integer :param deltay: pasted shape offset in y direction in pixels. :type deltay: integer """ self.TApp.paste_ctrl_v(Event())
class Player: def __init__(self, master): self.master = master pygame.init() pygame.mixer.init() #===Empty thread list=====# self.threads = [] #=====show an icon for the player===# def get_icon(): self.winicon = PhotoImage(file="best (2).png") master.iconphoto(False, self.winicon) #=====run the get_icon on a different thread from the gui=====# def icon(): mythreads = threading.Thread(target=get_icon) self.threads.append(mythreads) mythreads.start() icon() #=======all Button symbols and variables======# PLAY = "►" PAUSE = "║║" RWD = "⏮" FWD = "⏭" STOP = "■" UNPAUSE = "||" mute = "🔇" unmute = u"\U0001F50A" vol_mute = 0.0 vol_unmute = 1 #==========music playlist listbox=========# self.scroll = Scrollbar(master) self.play_list = Listbox(master, font="Sansarif 12 bold", bd=5, bg="white", width=37, height=19, selectbackground="black") self.play_list.place(x=600, y=77) self.scroll.place(x=946, y=80, height=389, width=15) self.scroll.config(command=self.play_list.yview) self.play_list.config(yscrollcommand=self.scroll.set) files = 'best (2).png' self.img1 = Image.open(files) self.img1 = self.img1.resize((600, 470), Image.ANTIALIAS) self.img = ImageTk.PhotoImage(self.img1) self.lab = Label(master) self.lab.grid(row=0, column=0) self.lab["compound"] = LEFT self.lab["image"] = self.img #=====show the song playing==========# self.var = StringVar() self.var.set( ".............................................................................." ) self.song_title = Label(master, font="Helvetica 12 bold", bg="black", fg="white", width=60, textvariable=self.var) self.song_title.place(x=3, y=0) # =====add a music list to the listbox======" def append_listbox(): global song_list try: directory = askdirectory() os.chdir(directory) # it permits to change the current dir song_list = os.listdir() song_list.reverse() for item in song_list: # it returns the list of files song pos = 0 self.play_list.insert(pos, item) pos += 1 global size index = 0 size = len(song_list) self.play_list.selection_set(index) self.play_list.see(index) self.play_list.activate(index) self.play_list.selection_anchor(index) except: showerror("File selected error", "Please choose a file correctly") # =====run the append_listbox function on separate thread====== # def add_songs_playlist(): mythreads = threading.Thread(target=append_listbox) self.threads.append(mythreads) mythreads.start() #=====show music time=========# def get_time(): current_time = pygame.mixer.music.get_pos() / 1000 formated_time = time.strftime("%H:%M:%S", time.gmtime(current_time)) next_one = self.play_list.curselection() song = self.play_list.get(next_one) song_timer = MP3(song) song_length = int(song_timer.info.length) format_for_length = time.strftime("%H:%M:%S", time.gmtime(song_length)) self.label_time.config( text=f"{ format_for_length} / {formated_time}") self.progress["maximum"] = song_length self.progress["value"] = int(current_time) master.after(100, get_time) #=====play the music====# def Play_music(): try: track = self.play_list.get(ACTIVE) pygame.mixer.music.load(track) self.var.set(track) pygame.mixer.music.play() get_time() # iterate through all the songs in the playlist # there is a bug when closing the window except: showerror("No Music", "Please load the music you want to play") def playAll(): try: index = 0 for i in range(size): self.play_list.select_clear(0, END) self.play_list.selection_set(index, last=None) self.play_list.see(index) self.play_list.activate(index) self.play_list.selection_anchor(index) track = self.play_list.get(index) pygame.mixer.music.load(track) self.var.set(track) pygame.mixer.music.play() current_song = self.play_list.curselection() song = self.play_list.get(current_song) song_timer = MP3(song) song_length = int(song_timer.info.length) * 1000 get_time() index += 1 except: showerror("No songs in playlist", "Please add music") def play_all(): mythreads = threading.Thread(target=playAll) self.threads.append(mythreads) mythreads.start() # ===pause and unpause == # def pause_unpause(): if self.button_pause['text'] == PAUSE: pygame.mixer.music.pause() self.button_pause['text'] = UNPAUSE elif self.button_pause['text'] == UNPAUSE: pygame.mixer.music.unpause() self.button_pause['text'] = PAUSE # ==play the music on a diffent thread from the gui == # def play_thread(): mythreads = threading.Thread(target=Play_music) self.threads.append(mythreads) mythreads.start() master.bind("<space>", lambda x: play_thread()) # ===stop=== def stop(): pygame.mixer.music.stop() #====increase and decrease volume when slider is moved()==# def volume(x): pygame.mixer.music.set_volume(self.volume_slider.get()) # ====mute and unmute the song while the song plays== # def muted(): if self.button_mute['text'] == unmute: pygame.mixer.music.set_volume(vol_mute) self.volume_slider.set(vol_mute) self.button_mute['fg'] = "red" self.button_mute['text'] = mute elif self.button_mute['text'] == mute: pygame.mixer.music.set_volume(vol_unmute) self.volume_slider.set(vol_unmute) self.button_mute['fg'] = "white" self.button_mute['text'] = unmute #===move to the next song===# def nextSong(): try: next_one = self.play_list.curselection() next_one = next_one[0] + 1 song = self.play_list.get(next_one) pygame.mixer.music.load(song) pygame.mixer.music.play() self.play_list.select_clear(0, END) self.play_list.activate(next_one) self.play_list.selection_set(next_one, last=None) self.var.set(song) get_time() self.play_list.see(next_one) except: showerror("No Next Song", "Please press the previous button") def next(): mythreads = threading.Thread(target=nextSong) self.threads.append(mythreads) mythreads.start() #===move to the previous song===# def prevSong(): try: next_one = self.play_list.curselection() next_one = next_one[0] - 1 song = self.play_list.get(next_one) pygame.mixer.music.load(song) pygame.mixer.music.play() self.play_list.select_clear(0, END) self.play_list.activate(next_one) self.play_list.selection_set(next_one, last=None) self.var.set(song) get_time() self.play_list.see(next_one) except: showerror("No previous Song", "Please press the Next button") def prev(): mythreads = threading.Thread(target=prevSong) self.threads.append(mythreads) mythreads.start() self.master.bind('<Left>', lambda x: prev()) self.master.bind('<Right>', lambda x: next()) #=====exit the application=====# def exit(): MsgBox = askquestion( 'Exit Application', 'Are you sure you want to exit the music player.', icon='warning') if MsgBox == 'yes': master.quit() master.after(100, exit) else: showinfo('Return', 'Continue playing your awesome music') return #=====Help window=====# def help(): top = Toplevel() top.title("Help") top.geometry("350x554+500+80") top.resizable(width=0, height=0) user_manual = [ " MUSIC PLAYER USER MANUAL: \n", "1. play button = ( ► )", "2. pause button = ║║ ", "3. unpause symbol = ||", "4. next button = ⏭ ", "5. previous button = ⏮", "6. mute button = '\U0001F50A' ", "7. unmute symbol = 🔇", "8. stop button = ■ ", "\n\n| Made by manucho | Copyright @ 2021 |\n" ] for i in user_manual: manual = Label(top, text=i, width=50, height=3, font="Helvetica, 11", bg="black", fg="white") manual.pack(side=TOP, fill=BOTH) #==============================================================================================# # THis part contains the menu, volume slider , music playlist label and the volume slider # #===============================================================================================# self.menu = Menu( self.lab, font="helvetica, 3", ) master.config(menu=self.menu) self.menu.add_command(label="HELP", command=help) self.menu.add_command(label="EXIT", command=exit) self.separator = ttk.Separator(self.lab, orient='horizontal') self.separator.place(relx=0, rely=0.87, relwidth=1, relheight=1) self.button_play = Button(master, text=PLAY, width=5, bd=5, bg="black", fg="white", font="Helvetica, 15", command=play_thread) self.button_play.place(x=150, y=415) self.button_stop = Button(master, text=STOP, width=5, bd=5, font="Helvetica, 15", bg="black", fg="white", command=stop) self.button_stop.place(x=225, y=415) self.button_prev = Button(master, text=FWD, width=5, bd=5, font="Helvetica, 15", bg="black", fg="white", command=next) self.button_prev.place(x=300, y=415) self.buttonPlayall = Button(self.master, text='\U0001F500', bg='black', fg='white', font='Helvetica, 15', bd=5, width=3, command=play_all) self.buttonPlayall.place(x=375, y=415) self.button_next = Button(master, text=RWD, width=5, bd=5, bg="black", fg="white", font="Helvetica, 15", command=prev) self.button_next.place(x=10, y=415) self.button_pause = Button(master, text=PAUSE, width=4, bd=5, font="Helvetica, 15", bg="black", fg="white", command=pause_unpause) self.button_pause.place(x=85, y=415) self.button_mute = Button(master, text=unmute, width=2, bd=5, font="Helvetica, 15", bg="black", fg="white", command=muted) self.button_mute.place(x=430, y=415) self.label_playlist = Label(master, text=u"♫ Music Playlist ♫ ", width=31, font="Helvetica, 15") self.label_playlist.place(x=610, y=5) self.button_load_music = Button( master, text="♫ Click Here To Load The Music ♫", width=43, bd=5, font="Helvetica, 10", bg="black", fg="white", command=add_songs_playlist) self.button_load_music.place(x=605, y=45) self.style = ttk.Style() self.style.configure("myStyle.Horizontal.TScale", background="#505050") self.volume_slider = ttk.Scale(self.lab, from_=0, to=1, orient=HORIZONTAL, value=1, length=120, command=volume, style="myStyle.Horizontal.TScale") self.volume_slider.place(x=475, y=424) self.progress = ttk.Progressbar(self.lab, orient=HORIZONTAL, value=0, length=453, mode='determinate') self.progress.place(x=0, y=385) self.label_time = Label(master, text="00:00:00 / 00:00:00", width=17, font="Helvetica, 10", bg="black", fg="white") self.label_time.place(x=460, y=387)
class SettingsFrame(Frame): """ Frame inheritance class for application settings and controls. """ def __init__(self, app, *args, **kwargs): """ Constructor """ self.__app = app # Reference to main application class self.__master = self.__app.get_master() # Reference to root class (Tk) Frame.__init__(self, self.__master, *args, **kwargs) # Initialise up key variables self._image_num = 0 self._image_name = "image" self._settype = StringVar() self._zoom = DoubleVar() self._radius = DoubleVar() self._exponent = IntVar() self._zx_off = DoubleVar() self._zy_off = DoubleVar() self._cx_off = DoubleVar() self._cy_off = DoubleVar() self._maxiter = IntVar() self._zx_coord = DoubleVar() self._zy_coord = DoubleVar() self._theme = StringVar() self._shift = IntVar() self._themes = None self._filename = StringVar() self._filepath = None self._frames = IntVar() self._zoominc = DoubleVar() self._autoiter = IntVar() self._autosave = IntVar() self._validsettings = True self.body() def body(self): """ Set up frame and widgets. """ # Create settings panel widgets # pylint: disable=W0108 self.lbl_settype = Label(self, text=LBLMODE) self.spn_settype = Spinbox( self, values=("BurningShip", "Tricorn", "Julia", "Mandelbrot"), width=8, bg=GOOD, wrap=True, textvariable=self._settype, ) self.lbl_zoom = Label(self, text=LBLZOOM) self.ent_zoom = Entry( self, border=2, relief="sunken", width=12, bg=GOOD, justify=RIGHT, textvariable=self._zoom, ) self.lbl_zoominc = Label(self, text=LBLZOOMINC, justify=LEFT) self.ent_zoominc = Entry(self, width=5, border=2, bg=GOOD, justify=RIGHT, textvariable=self._zoominc) self.lbl_zx_off = Label(self, text=LBLZXOFF) self.ent_zx_off = Entry( self, border=2, relief="sunken", width=12, bg=GOOD, justify=RIGHT, textvariable=self._zx_off, ) self.lbl_zy_off = Label(self, text=LBLZYOFF) self.ent_zy_off = Entry( self, border=2, relief="sunken", width=12, bg=GOOD, justify=RIGHT, textvariable=self._zy_off, ) self.lbl_cx_off = Label(self, text=LBLCX) self.ent_cx_off = Entry( self, border=2, relief="sunken", width=12, bg=GOOD, justify=RIGHT, textvariable=self._cx_off, state=DISABLED, ) self.lbl_cy_off = Label(self, text=LBLCY) self.ent_cy_off = Entry( self, border=2, relief="sunken", width=12, bg=GOOD, justify=RIGHT, textvariable=self._cy_off, state=DISABLED, ) self.lbl_niter = Label(self, text=LBLITER, justify=LEFT) self.ent_maxiter = Entry( self, border=2, relief="sunken", bg=GOOD, width=8, justify=RIGHT, textvariable=self._maxiter, ) self.chk_autoiter = Checkbutton(self, text="Auto", variable=self._autoiter, onvalue=1, offvalue=0) self.lbl_theme = Label(self, text=LBLTHEME, justify=LEFT) self.lbl_radius = Label(self, text=LBLRAD, justify=LEFT) self.ent_radius = Entry( self, border=2, relief="sunken", bg=GOOD, width=8, justify=RIGHT, textvariable=self._radius, ) self.lbl_exp = Label(self, text=LBLEXP) self.spn_exp = Spinbox( self, border=2, relief="sunken", bg=GOOD, width=4, from_=2, to=20, wrap=True, textvariable=self._exponent, ) self.sep_1 = ttk.Separator( self, orient=HORIZONTAL, ) self.lbx_theme = Listbox( self, border=2, relief="sunken", bg=GOOD, width=6, height=5, justify=LEFT, exportselection=False, ) self.lbl_shift = Label(self, text=LBLSHIFT, justify=LEFT) self.scl_shift = Scale( self, from_=0, to=100, orient=HORIZONTAL, variable=self._shift, border=2, relief="sunken", sliderlength=20, troughcolor=GOOD, ) self.scrollbar = Scrollbar(self, orient=VERTICAL) self.lbx_theme.config(yscrollcommand=self.scrollbar.set) self.scrollbar.config(command=self.lbx_theme.yview) self.lbx_theme.select_set(0) self.lbx_theme.event_generate("<<ListboxSelect>>") self.lbl_coords = Label(self, text="Re, Im", fg="grey") self.btn_plot = Button( self, text=BTNPLOT, width=8, fg="green", command=lambda: self.__app.frm_fractal.plot(), ) self.btn_cancel = Button( self, text=BTNCAN, width=8, command=lambda: self.__app.frm_fractal.cancel_press(), ) self.btn_reset = Button(self, text=BTNRST, width=8, command=self.reset) self.ent_save = Entry( self, textvariable=self._filename, width=6, border=2, relief="sunken", bg=GOOD, justify=LEFT, ) self._filename.set(self._image_name + str(self._image_num)) self.btn_save = Button(self, text=BTNSAVE, width=8, command=self.save_image) self.lbl_auto = Label(self, text=LBLAUTO, justify=LEFT) self.btn_autozoom = Button( self, text=BTNZOOM, width=8, command=lambda: self.__app.frm_fractal.animate_zoom(), ) self.btn_autospin = Button( self, text=BTNSPIN, width=8, command=lambda: self.__app.frm_fractal.animate_spin(), state=DISABLED, ) self.chk_autosave = Checkbutton(self, text=BTNSAVE, variable=self._autosave, onvalue=1, offvalue=0) self.lbl_frames = Label(self, text=FRMSTXT) self.ent_frames = Entry(self, width=5, border=2, bg=GOOD, justify=RIGHT, textvariable=self._frames) # Get list of available themes for idx, theme in enumerate(THEMES): self.lbx_theme.insert(idx, theme) self.body_arrange() # Position all widgets in frame self.reset() # Reset all settings to their defaults self.set_traces( ) # Trace entry variables for validation and event handling def body_arrange(self): """ Position widgets in frame """ # Position all widgets in their parent frames self.btn_plot.grid(column=0, row=1, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.btn_cancel.grid(column=1, row=1, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.btn_reset.grid(column=2, row=1, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.ent_save.grid(column=0, row=2, columnspan=2, sticky=(W, E), padx=3, pady=3) self.btn_save.grid(column=2, row=2, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.lbl_auto.grid(column=0, row=3, sticky=(W)) self.btn_autozoom.grid(column=1, row=3, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.btn_autospin.grid(column=2, row=3, ipadx=3, ipady=3, sticky=(W), padx=3, pady=3) self.lbl_frames.grid(column=0, row=4, sticky=(W)) self.ent_frames.grid(column=1, row=4, sticky=(W), padx=3, pady=3) self.chk_autosave.grid(column=2, row=4, sticky=(W), padx=3, pady=3) self.sep_1.grid(column=0, row=5, columnspan=3, pady=5, sticky=(W, E)) self.lbl_settype.grid(column=0, row=6, sticky=(W)) self.spn_settype.grid(column=1, row=6, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_zoom.grid(column=0, row=7, sticky=(W)) self.ent_zoom.grid(column=1, row=7, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_zoominc.grid(column=0, row=8, sticky=(W)) self.ent_zoominc.grid(column=1, row=8, sticky=(W), padx=3, pady=3) self.lbl_zx_off.grid(column=0, row=9, sticky=(W)) self.ent_zx_off.grid(column=1, row=9, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_zy_off.grid(column=0, row=10, sticky=(W)) self.ent_zy_off.grid(column=1, row=10, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_cx_off.grid(column=0, row=11, sticky=(W)) self.ent_cx_off.grid(column=1, row=11, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_cy_off.grid(column=0, row=12, sticky=(W)) self.ent_cy_off.grid(column=1, row=12, columnspan=2, sticky=(W, E), padx=3, pady=3) self.lbl_niter.grid(column=0, row=13, sticky=(W)) self.ent_maxiter.grid(column=1, row=13, sticky=(W), padx=3, pady=3) self.chk_autoiter.grid(column=2, row=13, sticky=(W), padx=3, pady=3) self.lbl_radius.grid(column=0, row=14, sticky=(W)) self.ent_radius.grid(column=1, row=14, sticky=(W), padx=3, pady=3) self.lbl_exp.grid(column=0, row=15, sticky=(W)) self.spn_exp.grid(column=1, row=15, sticky=(W), padx=3, pady=3) self.lbl_theme.grid(column=0, row=16, sticky=(W)) self.lbx_theme.grid(column=1, row=16, padx=3, pady=3, columnspan=2, sticky=(N, S, W, E)) self.scrollbar.grid(column=2, row=16, sticky=(N, S, E)) self.lbl_shift.grid(column=0, row=17, sticky=(W)) self.scl_shift.grid(column=1, row=17, columnspan=2, padx=3, pady=3, sticky=(W, E)) self.lbx_theme.bind("<<ListboxSelect>>", self.get_sel_theme) def set_traces(self): """ Set up entry variable traces for validation and event handling """ self._validsettings = True self._settype.trace("w", self.val_settings) self._zoom.trace("w", self.val_settings) self._zx_off.trace("w", self.val_settings) self._zy_off.trace("w", self.val_settings) self._cx_off.trace("w", self.val_settings) self._cy_off.trace("w", self.val_settings) self._maxiter.trace("w", self.val_settings) self._radius.trace("w", self.val_settings) self._exponent.trace("w", self.val_settings) self._filename.trace("w", self.val_settings) self._frames.trace("w", self.val_settings) self._zoominc.trace("w", self.val_settings) def val_settings(self, *args, **kwargs): """ Validate all user-entered settings. (A personal choice but I find this user experience more intuitive than the standard validatecommand method for Entry widgets) """ self._validsettings = True self.__app.set_status("") if self.is_float(self.ent_zoom.get()) and self._zoom.get() > 0: flg = GOOD else: flg = BAD self.flag_entry(self.ent_zoom, flg) if self.is_float(self.ent_zx_off.get()): flg = GOOD else: flg = BAD self.flag_entry(self.ent_zx_off, flg) if self.is_float(self.ent_zy_off.get()): flg = GOOD else: flg = BAD self.flag_entry(self.ent_zy_off, flg) if self.is_float(self.ent_cx_off.get()): flg = GOOD else: flg = BAD self.flag_entry(self.ent_cx_off, flg) if self.is_float(self.ent_cy_off.get()): flg = GOOD else: flg = BAD self.flag_entry(self.ent_cy_off, flg) if self.is_integer(self.ent_maxiter.get()) and self._maxiter.get() > 0: flg = GOOD else: flg = BAD self.flag_entry(self.ent_maxiter, flg) if self.is_float(self.ent_radius.get()) and self._radius.get() > 0: flg = GOOD else: flg = BAD self.flag_entry(self.ent_radius, flg) if self.is_integer(self.spn_exp.get()) and self._exponent.get() > 1: flg = GOOD else: flg = BAD self.flag_entry(self.spn_exp, flg) if self.is_integer(self.ent_frames.get()) and self._frames.get() > 0: flg = GOOD else: flg = BAD self.flag_entry(self.ent_frames, flg) if self.is_float(self.ent_zoominc.get()) and self._zoominc.get() > 0: flg = GOOD else: flg = BAD self.flag_entry(self.ent_zoominc, flg) if self.is_filename(self.ent_save.get()): flg = GOOD else: flg = BAD self.flag_entry(self.ent_save, flg) if self.spn_settype.get() in {"Mandelbrot", "Tricorn", "BurningShip"}: self.btn_autospin.config(state=DISABLED) self.ent_cx_off.config(state=DISABLED) self.ent_cy_off.config(state=DISABLED) self._cx_off.set(0) self._cy_off.set(0) flg = GOOD elif self.spn_settype.get() == "Julia": self.btn_autospin.config(state=NORMAL) self.ent_cx_off.config(state=NORMAL) self.ent_cy_off.config(state=NORMAL) flg = GOOD else: flg = BAD self.flag_entry(self.spn_settype, flg) def flag_entry(self, ent, flag): """ Flag entry field as valid or invalid and set global validity status flag. This flag is used throughout to determine whether functions can proceed or not. """ ent.config(bg=flag) if flag == BAD: self._validsettings = False self.__app.set_status(VALERROR, "red") def is_float(self, flag): """ Validate if entry is a float. """ try: float(flag) return True except ValueError: return False def is_integer(self, flag): """ Validate if entry is a positive integer. """ try: int(flag) if int(flag) > 0: return True return False except ValueError: return False def is_filename(self, flag): """ Validate if entry represents a valid filename using a regexp. """ return match("^[\w\-. ]+$", flag) and flag != "" def reset(self): """ Reset settings to defaults. """ self._settype.set("Mandelbrot") self._zoom.set(0.75) self._zx_off.set(-0.5) self._zy_off.set(0.0) self._cx_off.set(0.0) self._cy_off.set(0.0) self._maxiter.set(128) self._radius.set(2.0) self._exponent.set(2) self._frames.set(10) self._zoominc.set(2.0) self._autoiter.set(1) self._autosave.set(0) self._theme.set("Default") self._filename.set("image0") self._shift.set(0) self.set_sel_theme() self.__app.set_status(SETINITTXT) def get_sel_theme(self, *args, **kwargs): """ Get selected theme from listbox and set global variable. """ idx = self.lbx_theme.curselection() if idx == "": idx = 0 self._theme.set(self.lbx_theme.get(idx)) def set_sel_theme(self): """ Lookup index of selected theme and highlight that listbox position. NB: this requires 'exportselection=False' option to be set on listbox to work properly. """ for idx, theme in enumerate(THEMES): if theme == self._theme.get(): self.lbx_theme.activate(idx) self.lbx_theme.see(idx) break def get_settings(self): """ Return all current settings as a dict. """ if not self._validsettings: settings = {"valid": self._validsettings} return settings settings = { "settype": self._settype.get(), "zoom": self._zoom.get(), "zxoffset": self._zx_off.get(), "zyoffset": self._zy_off.get(), "cxoffset": self._cx_off.get(), "cyoffset": self._cy_off.get(), "maxiter": self._maxiter.get(), "autoiter": self._autoiter.get(), "autosave": self._autosave.get(), "radius": self._radius.get(), "exponent": self._exponent.get(), "theme": self._theme.get(), "shift": self._shift.get(), "filepath": self._filepath, "filename": self._filename.get(), "frames": self._frames.get(), "zoominc": self._zoominc.get(), "valid": self._validsettings, } return settings def update_settings(self, **kwargs): """ Update settings from keyword parms. """ if "settype" in kwargs: self._settype.set(kwargs["settype"]) if "zoom" in kwargs: self._zoom.set(kwargs["zoom"]) if "zxoffset" in kwargs: self._zx_off.set(kwargs["zxoffset"]) if "zyoffset" in kwargs: self._zy_off.set(kwargs["zyoffset"]) if "cxoffset" in kwargs: self._cx_off.set(kwargs["cxoffset"]) if "cyoffset" in kwargs: self._cy_off.set(kwargs["cyoffset"]) if "maxiter" in kwargs: self._maxiter.set(kwargs["maxiter"]) if "autoiter" in kwargs: self._autoiter.set(kwargs["autoiter"]) if "autosave" in kwargs: self._autosave.set(kwargs["autosave"]) if "radius" in kwargs: self._radius.set(kwargs["radius"]) if "exponent" in kwargs: self._exponent.set(kwargs["exponent"]) if "filepath" in kwargs: self._filepath.set(kwargs["filepath"]) if "filename" in kwargs: self._filename.set(kwargs["filename"]) if "frames" in kwargs: self._frames.set(kwargs["frames"]) if "zoominc" in kwargs: self._zoominc.set(kwargs["zoominc"]) if "theme" in kwargs: self._theme.set(kwargs["theme"]) self.set_sel_theme() if "shift" in kwargs: self._shift.set(kwargs["shift"]) def set_filepath(self): """ Sets filepath for saved files for the duration of this session. """ default = os.getcwd() # Default _filepath is current working directory if self._filepath is None: self._filepath = filedialog.askdirectory(title=SAVETITLE, initialdir=default, mustexist=True) if self._filepath == "": self._filepath = None # User cancelled return self._filepath def save_image(self): """ Save image as PNG file to selected filepath and automatically increment default image name. NB: currently this will overwrite any existing file of the same name without warning. """ # Bug out if the settings are invalid settings = self.__app.frm_settings.get_settings() if not settings["valid"]: return # Check if image has been created image = self.__app.frm_fractal.mandelbrot.get_image() if image is None: self.__app.set_status(NOIMGERROR, "red") return # Set _filename and path if self.set_filepath() is None: # User cancelled return fname = self._filename.get() fqname = self._filepath + "/" + fname # Save the image along with its metadata try: # image.write(fqname + ".png", format="png") image.save(fqname + ".png", format="png") self.save_metadata() except OSError: self.__app.set_status(SAVEERROR, "red") self._filepath = None return self._image_num += 1 self._filename.set(self._image_name + str(self._image_num)) self.__app.set_status(IMGSAVETXT + fqname + ".png", "green") # Return focus to image frame self.__app.frm_fractal.focus_set() def save_metadata(self): """ Save json file containing meta data associated with image, allowing it to be imported and reproduced. """ if self._filepath is None: if self.set_filepath() is None: # User cancelled return fname = self._filename.get() fqname = self._filepath + "/" + fname filename = fqname + ".json" createtime = strftime("%b %d %Y %H:%M:%S %Z", gmtime()) jsondata = { MODULENAME: { "filename": fqname + ".png", "created": createtime, "settype": self._settype.get(), "zoom": self._zoom.get(), "zoominc": self._zoominc.get(), "frames": self._frames.get(), "escradius": self._radius.get(), "exponent": self._exponent.get(), "maxiter": self._maxiter.get(), "zxoffset": self._zx_off.get(), "zyoffset": self._zy_off.get(), "cxoffset": self._cx_off.get(), "cyoffset": self._cy_off.get(), "theme": self._theme.get(), "shift": self._shift.get(), } } try: with open(filename, "w") as outfile: dump(jsondata, outfile) except OSError: self.__app.set_status(METASAVEERROR, "red") self._filepath = None # Return focus to image frame self.__app.frm_fractal.focus_set() def import_metadata(self): """ Update settings from imported json metadata file. """ # Select and read file try: default = os.getcwd() filepath = filedialog.askopenfilename( initialdir=default, title=SELTITLE, filetypes=(("json files", "*.json"), ("all files", "*.*")), ) if filepath == "": # User cancelled return with open(filepath, "r") as infile: jsondata = infile.read() except OSError: self.__app.set_status(OPENFILEERROR, "red") return # Parse file try: settings = loads(jsondata).get(MODULENAME) # Set plot parameters self._settype.set(settings.get("settype", "Mandelbrot")) self._zoom.set(settings.get("zoom", 1)) self._zoominc.set(settings.get("zoominc", 2.0)) self._frames.set(settings.get("frames", 10)) self._radius.set(settings.get("escradius", 2.0)) self._exponent.set(settings.get("exponent", 2)) self._maxiter.set(settings.get("maxiter", 256)) self._zx_off.set(settings.get("zxoffset", 0)) self._zy_off.set(settings.get("zyoffset", 0)) self._cx_off.set(settings.get("cxoffset", 0)) self._cy_off.set(settings.get("cyoffset", 0)) self._theme.set(settings.get("theme", "Default")) self._shift.set(settings.get("shift", 0)) except OSError: self.__app.set_status(BADJSONERROR, "red") return self.set_sel_theme() fbase = os.path.basename(filepath) filename, fileext = os.path.splitext(fbase) self.__app.set_status( "Metadata file " + filename + fileext + METAPROMPTTXT, "green") # Return focus to image frame self.__app.frm_fractal.focus_set()
class SearchGUI: def update_db(self): for user in self.users_dict.values(): Q = Query() users_db.update(user, Q.user_id == user['user_id']) def add_user_history(self, index, history): self.users_dict[index]['history'].append(history) self.update_db() def add_user_selection(self, index, selection): self.users_dict[index]['selections'].append(selection) self.update_db() def get_selected_user(self): selection = self.users_listbox.curselection() if len(selection) > 0: index = selection[0] return index, self.users_dict[index] elif self.results_user is not None: return self.results_user, self.users_dict[self.results_user] else: return None, None def selections_release_year_function(self): index, user = self.get_selected_user() if index is not None and user is not None: years = [] selections = user['selections'] if len(selections) <= 0: return [] for selection in selections: source = selection['_source'] years.append(source['release_year']) return [{ "exp": { "release_year": { "origin": sum(years) / len(years), "offset": 2, "scale": stdev(years), "decay": 0.5 } }, "weight": 1 }] else: return [] def selections_description_function(self): index, user = self.get_selected_user() if index is not None and user is not None: descriptions = [] selections = user['selections'] if len(selections) <= 0: return [] for selection in selections: source = selection['_source'] descriptions.append(source['description']) desc = ' '.join(descriptions) return [{ "filter": { "match": { "description": { "query": desc, "auto_generate_synonyms_phrase_query": True } } }, "weight": 2 }] else: return [] def history_function(self): index, user = self.get_selected_user() if index is not None and user is not None: if user['user_id'] < 0: return [] queries = [] history = user['history'] if len(history) <= 0: return [] for entry in history: queries.append(entry['query']) tmp = ' '.join(queries) return [{ "filter": { "multi_match": { "query": tmp, "type": "best_fields", "fields": ["title", "description", "cast", "director"] } }, "weight": 1 }] else: return [] def selections_listed_in_functions(self): index, user = self.get_selected_user() if index is not None and user is not None: selections = user['selections'] if len(selections) <= 0: return [] listed_in = dict() nbr_selections = len(selections) for selection in selections: source = selection['_source'] categories = source['listed_in'].split(', ') for category in categories: if category in listed_in: listed_in[category] = listed_in.get(category) + 1 else: listed_in[category] = 1 return [{ "filter": {"match_phrase": { "listed_in": c } }, "weight": (listed_in[c] / nbr_selections) * 4 } for c in listed_in.keys()] else: return {} def selections_type_functions(self): index, user = self.get_selected_user() if index is not None and user is not None: selections = user['selections'] if len(selections) <= 0: return [] movies = 0 tv_shows = 0 nbr_selections = len(selections) for selection in selections: source = selection['_source'] category = source['type'] if category == 'Movie': movies += 1 else: tv_shows += 1 if movies > tv_shows: movies_percentage = movies / (movies + tv_shows) if movies_percentage > 0.75: return [{ "filter": { "match": { "type": 'Movie' } }, "weight": movies_percentage * 2 }] else: tv_shows_percentage = tv_shows / (movies + tv_shows) if tv_shows_percentage > 0.75: return [{"filter": {"match": {"type": 'TV Show'}}, "weight": tv_shows_percentage * 2}] return [] def search(self, query): index, user = self.get_selected_user() if index is not None and user is not None: functions = self.selections_listed_in_functions() \ + self.selections_type_functions() \ + self.selections_release_year_function() \ + self.selections_description_function() \ + self.history_function() self.error_message.set("") body = get_dsl(query, functions) # print(body) response = es.search(body=body, index="netflix") hits = response['hits']['hits'] timestamp = datetime.now().timestamp() self.add_user_history(index, {'timestamp': timestamp, 'query': query}) self.update_result(index, hits) self.update_shown_profile() else: self.error_message.set("select a user before searching") def update_result(self, index, results): nbr_items = self.result_listbox.size() self.result_listbox.delete(0, nbr_items) self.results_user = index self.results.clear() for index, result in enumerate(results): source = result['_source'] score = float(result['_score']) self.results.insert(index, result) # self.result_listbox.insert(index, "%.3f \t %s \t %s \t %s"%(score, str(source['release_year']), source['title'], source['listed_in'])) self.result_listbox.insert(index, "%s \t %s \t\t %s \t %s \t %s"% (source['release_year'], source['type'], source['title'], source['description'], source['listed_in'])) if index < 20: print("%s \t %s \t %s \t %s \t %s" % ( source['title'], source['type'], source['release_year'], source['listed_in'], source['description'])) def update_shown_profile(self): index, user = self.get_selected_user() if index is not None and user is not None: nbr_history = self.history_listbox.size() self.history_listbox.delete(0, nbr_history) for index, result in enumerate(user['history']): self.history_listbox.insert(index, "%s \t\t %s" % (result['timestamp'], result['query'])) nbr_selections = self.selections_listbox.size() self.selections_listbox.delete(0, nbr_selections) for index, result in enumerate(user['selections']): source = result['_source'] self.selections_listbox.insert(index, "%s \t %s \t\t %s \t %s \t %s"% (source['release_year'], source['type'], source['title'], source['description'], source['listed_in'])) def select(self): selection = self.result_listbox.curselection() user_index = self.results_user if len(selection) > 0 and user_index is not None: item = self.results[selection[0]] self.add_user_selection(user_index, item) self.update_shown_profile() else: self.error_message.set("perform a search first") def update_users(self): users = users_db.all() nbr_items = self.users_listbox.size() self.users_listbox.delete(0, nbr_items) for index, result in enumerate(users): self.users_dict[index] = result self.users_listbox.insert(index, "%s \t\t %s" % (result['user_id'], result['name'])) def __init__(self, master): self.master = master master.title("Search engine") self.users_dict = dict() self.results_user = None self.results = list() self.users_listbox = Listbox(self.master, selectmode="SINGLE", width=200) self.users_listbox.grid(column=0, row=0, columnspan=10) self.update_users() self.users_listbox.activate(0) self.query = StringVar() self.entry = Entry(self.master, textvariable=self.query, width=160) self.entry.grid(column=0, row=1, columnspan=10) Button(self.master, command=lambda: self.search(self.query.get()), text="Search", width=20).grid(column=0, row=2, columnspan=10) Label(self.master, text="Results").grid(column=0, row=3, columnspan=10) self.result_listbox = Listbox(self.master, selectmode="SINGLE", width=200) self.result_listbox.grid(column=0, row=4, columnspan=10) Button(self.master, command=lambda: self.select(), text="Select", width=40).grid(column=0, row=5, columnspan=10) Label(self.master, text="History").grid(column=0, row=6, columnspan=5) self.history_listbox = Listbox(self.master, selectmode="SINGLE", width=100) self.history_listbox.grid(column=0, row=7, columnspan=5) Label(self.master, text="Selections").grid(column=5, row=6, columnspan=5) self.selections_listbox = Listbox(self.master, selectmode="SINGLE", width=100) self.selections_listbox.grid(column=5, row=7, columnspan=5) self.error_message = StringVar() Label(self.master, textvariable=self.error_message, fg="red").grid(column=0, row=8, columnspan=10)
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 AutocompleteEntry(Entry): def __init__(self, autocompleteList, *args, **kwargs): # Listbox length if 'listboxLength' in kwargs: self.listboxLength = kwargs['listboxLength'] del kwargs['listboxLength'] else: self.listboxLength = 8 # Custom matches function if 'matchesFunction' in kwargs: self.matchesFunction = kwargs['matchesFunction'] del kwargs['matchesFunction'] else: def matches(fieldValue, acListEntry): pattern = re.compile('.*' + re.escape(fieldValue) + '.*', re.IGNORECASE) return re.match(pattern, acListEntry) self.matchesFunction = matches Entry.__init__(self, *args, **kwargs) self.focus() self.autocompleteList = autocompleteList self.var = self["textvariable"] if self.var == '': self.var = self["textvariable"] = StringVar() self.var.trace('w', self.changed) self.bind("<Right>", self.selection) self.bind("<Up>", self.moveUp) self.bind("<Down>", self.moveDown) self.listboxUp = False def changed(self, name, index, mode): if self.var.get() == '': if self.listboxUp: self.listbox.destroy() self.listboxUp = False else: words = self.comparison() if words: if not self.listboxUp: self.listbox = Listbox(width=self["width"], height=self.listboxLength) self.listbox.bind("<Button-1>", self.selection) self.listbox.bind("<Right>", self.selection) self.listbox.place(x=self.winfo_x(), y=self.winfo_y() + self.winfo_height()) self.listboxUp = True self.listbox.delete(0, END) for w in words: self.listbox.insert(END, w) else: if self.listboxUp: self.listbox.destroy() self.listboxUp = False def selection(self, event): if self.listboxUp: self.var.set(self.listbox.get(ACTIVE)) self.listbox.destroy() self.listboxUp = False self.icursor(END) def moveUp(self, event): if self.listboxUp: if self.listbox.curselection() == (): index = '0' else: index = self.listbox.curselection()[0] if index != '0': self.listbox.selection_clear(first=index) index = str(int(index) - 1) self.listbox.see(index) # Scroll! self.listbox.selection_set(first=index) self.listbox.activate(index) def moveDown(self, event): if self.listboxUp: if self.listbox.curselection() == (): index = '0' else: index = self.listbox.curselection()[0] if index != END: self.listbox.selection_clear(first=index) index = str(int(index) + 1) self.listbox.see(index) # Scroll! self.listbox.selection_set(first=index) self.listbox.activate(index) def comparison(self): return [ w for w in self.autocompleteList if self.matchesFunction(self.var.get(), w) ]
class Combobox_Autocomplete(Entry, object): def __init__(self, master, list_of_items=None, autocomplete_function=None, listbox_width=None, listbox_height=7, ignorecase_match=False, startswith_match=True, vscrollbar=True, hscrollbar=True, **kwargs): if hasattr(self, "autocomplete_function"): if autocomplete_function is not None: raise ValueError( "Combobox_Autocomplete subclass has 'autocomplete_function' implemented") else: if autocomplete_function is not None: self.autocomplete_function = autocomplete_function else: if list_of_items is None: raise ValueError( "If not given complete function, list_of_items can't be 'None'") elif ignorecase_match: if startswith_match: def matches_function(entry_data, item): return item.startswith(entry_data) else: def matches_function(entry_data, item): return item in entry_data self.autocomplete_function = lambda entry_data: [ item for item in self.list_of_items if matches_function(entry_data, item)] else: if startswith_match: def matches_function(escaped_entry_data, item): if re.match(escaped_entry_data, item, re.IGNORECASE): return True else: return False else: def matches_function(escaped_entry_data, item): if re.search(escaped_entry_data, item, re.IGNORECASE): return True else: return False def autocomplete_function2(entry_data): escaped_entry_data = re.escape(entry_data) return [item for item in self.list_of_items if matches_function(escaped_entry_data, item)] self.autocomplete_function = autocomplete_function2 self._listbox_height = int(listbox_height) self._listbox_width = listbox_width self.list_of_items = list_of_items self._use_vscrollbar = vscrollbar self._use_hscrollbar = hscrollbar kwargs.setdefault("background", "white") if "textvariable" in kwargs: self._entry_var = kwargs["textvariable"] else: self._entry_var = kwargs["textvariable"] = StringVar() Entry.__init__(self, master, **kwargs) self._trace_id = self._entry_var.trace('w', self._on_change_entry_var) self._listbox = None self.bind("<Tab>", self._on_tab) self.bind("<Up>", self._previous) self.bind("<Down>", self._next) self.bind('<Control-n>', self._next) self.bind('<Control-p>', self._previous) self.bind("<Return>", self._update_entry_from_listbox) self.bind("<Escape>", lambda event: self.unpost_listbox()) def _on_tab(self, event): self.post_listbox() return "break" def _on_change_entry_var(self, name, index, mode): entry_data = self._entry_var.get() if entry_data == '': self.unpost_listbox() self.focus() else: values = self.autocomplete_function(entry_data) if values: if self._listbox is None: self._build_listbox(values) else: self._listbox.delete(0, END) height = min(self._listbox_height, len(values)) self._listbox.configure(height=height) for item in values: self._listbox.insert(END, item) else: self.unpost_listbox() self.focus() def _build_listbox(self, values): listbox_frame = Frame() self._listbox = Listbox(listbox_frame, background="white", selectmode=SINGLE, activestyle="none", exportselection=False) self._listbox.grid(row=0, column=0, sticky=N+E+W+S) self._listbox.bind("<ButtonRelease-1>", self._update_entry_from_listbox) self._listbox.bind("<Return>", self._update_entry_from_listbox) self._listbox.bind("<Escape>", lambda event: self.unpost_listbox()) self._listbox.bind('<Control-n>', self._next) self._listbox.bind('<Control-p>', self._previous) if self._use_vscrollbar: vbar = Scrollbar(listbox_frame, orient=VERTICAL, command=self._listbox.yview) vbar.grid(row=0, column=1, sticky=N+S) self._listbox.configure( yscrollcommand=lambda f, l: autoscroll(vbar, f, l)) elif self._use_hscrollbar: hbar = Scrollbar(listbox_frame, orient=HORIZONTAL, command=self._listbox.xview) hbar.grid(row=1, column=0, sticky=E+W) self._listbox.configure( xscrollcommand=lambda f, l: autoscroll(hbar, f, l)) listbox_frame.grid_columnconfigure(0, weight=1) listbox_frame.grid_rowconfigure(0, weight=1) x = -self.cget("borderwidth") - self.cget("highlightthickness") y = self.winfo_height()-self.cget("borderwidth") - \ self.cget("highlightthickness") elif self._listbox_width: width = self._listbox_width else: width = self.winfo_width() listbox_frame.place(in_=self, x=x, y=y, width=width) height = min(self._listbox_height, len(values)) self._listbox.configure(height=height) for item in values: self._listbox.insert(END, item) def post_listbox(self): if self._listbox is not None: return entry_data = self._entry_var.get() if entry_data == '': return values = self.autocomplete_function(entry_data) if values: self._build_listbox(values) def unpost_listbox(self): if self._listbox is not None: self._listbox.master.destroy() self._listbox = None def get_value(self): return self._entry_var.get() def set_value(self, text, close_dialog=False): self._set_var(text) if close_dialog: self.unpost_listbox() self.icursor(END) self.xview_moveto(1.0) def _set_var(self, text): self._entry_var.trace_vdelete("w", self._trace_id) self._entry_var.set(text) self._trace_id = self._entry_var.trace('w', self._on_change_entry_var) def _update_entry_from_listbox(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if current_selection: text = self._listbox.get(current_selection) self._set_var(text) self._listbox.master.destroy() self._listbox = None self.focus() self.icursor(END) self.xview_moveto(1.0) return "break" def _previous(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if len(current_selection) == 0: self._listbox.selection_set(0) self._listbox.activate(0) else: index = int(current_selection[0]) self._listbox.selection_clear(index) if index == 0: index = END else: index -= 1 self._listbox.see(index) self._listbox.selection_set(first=index) self._listbox.activate(index) return "break" def _next(self, event): if self._listbox is not None: current_selection = self._listbox.curselection() if len(current_selection) == 0: self._listbox.selection_set(0) self._listbox.activate(0) else: index = int(current_selection[0]) self._listbox.selection_clear(index) if index == self._listbox.size() - 1: index = 0 else: index += 1 self._listbox.see(index) self._listbox.selection_set(index) self._listbox.activate(index) return "break"
class MyGUI: def __init__(self, master): # Create the main window widget. self.sleep = True self.DATA = [] self.colors = { 'white': '#FFFFFF', 'blue': '#2B547E', 'black': '#000000', 'red': '#FF3346', 'green': '#306754', 'grey': '#E5E4E2', 'hex': '#9bcbff', 'button_color': '#C0C0C0' } self.check = 0 self.chosenSong = '' self.chosenId = 0 self.POP = [] self.CHILL = [] self.DANCE = [] self.check_pop = IntVar() self.check_chill = IntVar() self.check_dance = IntVar() self.master = master self.master.title('Music Player') self.master.configure(bg=self.colors['hex']) self.master.geometry('850x600') self.f_name = '' self.imagePath = tk.PhotoImage(file='Logo.png') self.rewindImg = tk.PhotoImage(file='rewind.png') self.playImage = tk.PhotoImage(file='play.png') self.pauseImage = tk.PhotoImage(file='pause.png') self.nextImage = tk.PhotoImage(file='unpause.png') self.image = tk.Label(master, image=self.imagePath, borderwidth=0, bg=self.colors['hex']) # widgets self.file_name_label = Label( master, fg=self.colors['black'], bg=self.colors['button_color'], text='Enter a name for your file: ', width=57, font='Helvetica 10 bold') self.pop = Checkbutton( master, fg=self.colors['black'], bg=self.colors['hex'], text='POP', highlightbackground=self.colors['black'], variable=self.check_pop) self.chill = Checkbutton( master, fg=self.colors['black'], bg=self.colors['hex'], text='CHILL', highlightbackground=self.colors['black'], variable=self.check_chill) self.dance = Checkbutton( master, fg=self.colors['black'], bg=self.colors['hex'], text='DANCE', highlightbackground=self.colors['black'], variable=self.check_dance) self.file_name = Text( master, fg=self.colors['blue'], bg=self.colors['grey'], width=57, height=1) self.explorer_label1 = Button( master, fg=self.colors['grey'], bg=self.colors['blue'], width=30, text='Download Tracks/Show Genres', command=self.download) self.explorer = Listbox( master, fg=self.colors['blue'], bg=self.colors['grey'], width=70, height=15, highlightcolor=self.colors['green']) self.explorer2 = Listbox( master, fg=self.colors['blue'], bg=self.colors['grey'], width=70, height=15, highlightcolor=self.colors['green']) self.search_btn = Button( master, fg=self.colors['grey'], bg=self.colors['blue'], width=30, text='Search', command=self.showGenre) self.delete_button = Button( master, fg=self.colors['white'], bg=self.colors['red'], text='DELETE', width=31, highlightbackground=self.colors['black'], command=self.delete) self.stop_button = Button( master, fg=self.colors['grey'], image=self.pauseImage, bg=self.colors['green'], text='PAUSE', width=80, font='Helvetica 10 bold', highlightbackground=self.colors['black'], command=self.stop) self.unpause_button = Button( master, fg=self.colors['grey'], image=self.playImage, bg=self.colors['green'], text='PAUSE', width=80, font='Helvetica 10 bold', highlightbackground=self.colors['black'], command=self.unpause) self.play_button = Button( master, image=self.playImage, fg=self.colors['grey'], bg=self.colors['green'], text='PLAY', width=80, highlightbackground=self.colors['black'], command=self.play) self.status_label = Label( master, fg=self.colors['grey'], bg=self.colors['black'], width=60, text='Hello') self.clear_button = Button( master, fg=self.colors['grey'], bg=self.colors['green'], text='CLEAR', width=15, highlightbackground=self.colors['black'], command=self.clear) self.rewind_button = Button( master, image=self.rewindImg, fg=self.colors['grey'], bg=self.colors['green'], text='REWIND', width=80, highlightbackground=self.colors['black'], command=self.rewind) self.next_button = Button( master, image=self.nextImage, fg=self.colors['grey'], bg=self.colors['green'], text='REWIND', width=80, highlightbackground=self.colors['black'], command=self.nextSelection) # grid self.image.grid(row=0, sticky=W + E) self.explorer.grid(row=7, sticky=W, padx=20) self.explorer2.grid(row=7, sticky=E, padx=20) self.play_button.grid(row=8, sticky=W, padx=100, pady=20) self.delete_button.grid(row=8, sticky=E, padx=20, pady=20) self.search_btn.grid(row=6, sticky=E, padx=20) self.status_label.grid(row=13, sticky=E + W, padx=20) self.rewind_button.grid(row=8, sticky=W, padx=20, pady=20) self.next_button.grid(row=8, sticky=W, padx=180, pady=20) self.file_name.grid(row=2, sticky=E, padx=20) self.displayAllData() if self.checkingConnection(): self.file_name_label.grid(row=2, sticky=W, padx=20) self.file_name.grid(row=2, sticky=E, padx=20) self.pop.grid(row=3, sticky=W, padx=100, pady=5) self.chill.grid(row=3, pady=5) self.dance.grid(row=3, sticky=E, padx=100, pady=5) self.explorer_label1.grid(row=6, sticky=W, padx=20) self.clear_button.grid(row=8, sticky=W, padx=350, pady=20) # search_for_Genre def showGenre(self): keywords = self.file_name.get('1.0', END).strip() if self.check_pop.get() == 1 or keywords == 'POP': self.explorer.delete(0, 'end') self.getAllPopSongs() elif self.check_chill.get() == 1 or keywords == 'CHILL': self.explorer.delete(0, 'end') self.getAllChillSongs() elif self.check_dance.get() == 1 or keywords == 'DANCE': self.explorer.delete(0, 'end') self.getAllDanceSongs() elif keywords == 'ALL': self.explorer.delete(0, 'end') self.displayAllData() else: self.explorer.delete(0, 'end') self.search(keywords) # download def download(self): self.POP = [] self.CHILL = [] self.DANCE = [] self.f_name = self.file_name.get('1.0', END).strip() options = { 'default_search': 'ytsearch1', 'quiet': True } ydl = YoutubeDL(options) search_result = ydl.extract_info(self.f_name, download=False) songs = search_result["entries"] self.downloadSong(songs[0]['id']) obj = { 'ID': songs[0]['id'], 'Title': self.f_name, 'Creator': songs[0]['creator'], 'Duration': songs[0]['duration'], 'Alternative Title': songs[0]['alt_title'] } if self.check_dance.get() == 1: self.DANCE.append(obj) elif self.check_chill.get() == 1: self.CHILL.append(obj) elif self.check_pop.get() == 1: self.POP.append(obj) self.saveData(obj) self.showGenre() def play(self): self.check = self.check + 1 try: self.clear() self.displayIn4() self.chosenId = '' except Exception as FormatError: self.explorer2.insert("0", 'Format Error') chosenSong = self.explorer.get(ACTIVE).strip() self.chosenId = self.searchReturnId(chosenSong) os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" mixer.init() mixer.music.load("D:\\MusicPlayer\\musicPlayer\\musicdownloads\\" + self.chosenId + ".mp3") if self.check == 1: self.play_button.grid_forget() self.stop_button.grid(row=8, sticky=W, padx=100, pady=20) mixer.music.play() def rewind(self): mixer.music.rewind() def stop(self): self.check = self.check + 1 if self.check % 2 == 0: self.unpause_button.grid(row=8, sticky=W, padx=100, pady=20) mixer.music.pause() self.stop_button.grid_forget() def unpause(self): self.check = self.check + 1 if self.check % 2 != 0: self.stop_button.grid(row=8, sticky=W, padx=100, pady=20) self.unpause_button.grid_forget() mixer.music.unpause() def delete(self): deleting = self.explorer.get(ACTIVE).strip() dataPop = [] dataChill = [] dataDance = [] allData = [] a = {} allData = self.getAlldata() for item in range(len(allData)): if allData[item]['Title'] == deleting: a = allData[item] allData.remove(a) with open('data.json', 'w', encoding="utf8") as data_file: jsonData = json.dumps(allData) data_file.write(jsonData) openPop = open('pop.json') dataPop = json.load(openPop) openPop.close() tempK = {} for k in (dataPop): if k == a: tempK = k dataPop.remove(tempK) with open('pop.json', 'w', encoding="utf8") as pop_file: popData = json.dumps(dataPop) pop_file.write(popData) openChill = open('chill.json') dataChill = json.load(openChill) openChill.close() tempC = {} for c in (dataChill): if c == a: tempC = c dataChill.remove(tempC) with open('chill.json', 'w', encoding="utf8") as chill_file: chillData = json.dumps(dataChill) chill_file.write(chillData) openDance = open('dance.json') dataDance = json.load(openDance) openDance.close() tempD = {} for d in (dataDance): if d == a: tempD = d dataChill.remove(tempD) with open('dance.json', 'w', encoding="utf8") as dance_file: danceData = json.dumps(dataDance) dance_file.write(danceData) def saveData(self, obj): self.DATA.append(obj) data = [] input_data = open('data.json') data = json.load(input_data) data = data + self.DATA input_data.close() with open('data.json', 'w', encoding="utf8") as data_file: jsonData = json.dumps(data) data_file.write(jsonData) def sort(self, arr): n = len(arr) # Traverse through all array elements for i in range(n): # Last i elements are already in place for j in range(0, n - i - 1): # traverse the array from 0 to n-i-1 # Swap if the element found is greater # than the next element if arr[j]['Title'] > arr[j + 1]['Title']: arr[j], arr[j + 1] = arr[j + 1], arr[j] def clear(self): self.file_name.delete('1.0', END) self.check_pop.set(0) self.check_dance.set(0) self.check_chill.set(0) self.explorer2.delete('0', END) def search(self, name): dataSearching = self.getAlldata() l = 0 r = len(dataSearching) - 1 self.sort(dataSearching) while l <= r: mid = int(l + (r - l) / 2); # Check if x is present at mid if dataSearching[mid]['Title'] == name: self.explorer.insert(0, dataSearching[mid]['Title']) break # If x is greater, ignore left half elif dataSearching[mid]['Title'] < name: l = mid +1 # If x is smaller, ignore right half else: r = mid - 1 def checkingConnection(self): try: urlopen('https://www.google.com', timeout=1) return True except urllib.error.URLError as Error: print(Error) return False def searchReturnId(self, name): dataSearchingID = self.getAlldata() l = 0 r = len(dataSearchingID) - 1 self.sort(dataSearchingID) while l <= r: mid = (l + (r - l) // 2); # Check if x is present at mid if dataSearchingID[mid]['Title'] == name: return dataSearchingID[mid]["ID"] # If x is greater, ignore left half elif dataSearchingID[mid]['Title'] < name: l = mid + 1 # If x is smaller, ignore right half else: r = mid - 1 def displayAllData(self): allData = self.getAlldata() self.sort(allData) for a in range(len(allData)): self.explorer.insert(len(allData), allData[a]['Title']) def downloadSong(self, id): options = { # specify the path to download 'outtmpl': 'musicdownloads/%(id)s', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', # Tách lấy audio 'preferredcodec': 'mp3', # Format ưu tiên là mp3 'preferredquality': '192', # Chất lượng bitrate }] } ydl = YoutubeDL(options) # download() có thể truyền vào 1 str hoặc 1 list ydl.download(["https://www.youtube.com/watch?v={}".format(id)]) def displayIn4(self): string = self.explorer.get(ACTIVE).strip() data = self.getAlldata() obj = {} for x in (data): if x['Title'] == string: obj = x break values = obj.values() for k, v in obj.items(): self.explorer2.insert(len(values), k + ": " + str(v)) def getAlldata(self): allData = [] input_AllData = open('data.json') allData = json.load(input_AllData) input_AllData.close() return allData def nextSelection(self): if self.sleep: sleep(2) self.check = 0 selection_indices = self.explorer.curselection() # default next selection is the beginning next_selection = 0 # make sure at least one item is selected if len(selection_indices) > 0: # Get the last selection, remember they are strings for some reason # so convert to int last_selection = int(selection_indices[-1]) # clear current selections self.explorer.selection_clear(selection_indices) # Make sure we're not at the last item if last_selection < self.explorer.size() - 1: next_selection = last_selection + 1 self.explorer.activate(next_selection) self.explorer.selection_set(next_selection) self.play() def getAllChillSongs(self): chillLst = [] input_chill = open('chill.json') chillLst = json.load(input_chill) chillLst = chillLst + self.CHILL input_chill.close() with open('chill.json', 'w', encoding="utf8") as chill_file: jsonChill = json.dumps(chillLst) chill_file.write(jsonChill) self.sort(chillLst) for y in range(len(chillLst)): self.explorer.insert(len(chillLst), chillLst[y]['Title']) def getAllDanceSongs(self): danceLst = [] input_dance = open('dance.json') danceLst = json.load(input_dance) danceLst = danceLst + self.DANCE input_dance.close() with open('dance.json', 'w', encoding="utf8") as dance_file: jsonDance = json.dumps(danceLst) dance_file.write(jsonDance) self.sort(danceLst) for d in range(len(danceLst)): self.explorer.insert(len(danceLst), danceLst[d]['Title']) def getAllPopSongs(self): popLst = [] input_pop = open('pop.json') popLst = json.load(input_pop) danceLst = popLst + self.DANCE input_pop.close() with open('dance.json', 'w', encoding="utf8") as dance_file: jsonDance = json.dumps(popLst) dance_file.write(jsonDance) self.sort(popLst) for d in range(len(popLst)): self.explorer.insert(len(popLst), popLst[d]['Title'])