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()
def initUI(self): self.parent.title("TVstream manager") self.stytle = ttk.Style() self.pack(fill=BOTH, expand=1) self.columnconfigure(0, weight=1, pad=2) self.columnconfigure(2, weight=1, pad=2) self.columnconfigure(4, pad=7) #self.rowconfigure(3, weight=1) #self.rowconfigure(4, weight=1, pad=7) lbll = Label(self, text="Lingua") lbll.grid(row=0,column=0,sticky=W, pady=4, padx=5) lble = Label(self, text="Emittenti") lble.grid(row=0,column=2,sticky=W, pady=1, padx=5) global langlst scrollang = Scrollbar(self) langlst = Listbox(self,font='Arial 9',yscrollcommand=scrollang.set) scrollang.config(command = langlst.yview) for i in lingue: langlst.insert(END, i) langlst.focus_set() langlst.bind("<<ListboxSelect>>", self.onSelectLang) langlst.grid(row=1,column=0, columnspan=2, padx=6,sticky=E+W+S+N) scrollang.grid(row=1,column=1, sticky=E+S+N) global emitlst scrollemit = Scrollbar(self) emitlst = Listbox(self,font='Arial 9',yscrollcommand=scrollemit.set) scrollemit.config(command = emitlst.yview ) emitlst.bind("<<ListboxSelect>>", self.onSelectEmittente) emitlst.grid(row=1,column=2, columnspan=2, padx=5,sticky=E+W+S+N) scrollemit.grid(row=1,column=3,sticky=E+S+N) lbltxt = Label(self, text="Output log") lbltxt.grid(row=2,column=0, columnspan=3, sticky=W, pady=4, padx=5) global area area = Text(self,height=10,font='Arial 9') area.grid(row=3, column=0, columnspan=5, rowspan=1, padx=5, sticky=E+W+S+N) scrolltxt = Scrollbar(self) scrolltxt.config(command = area.yview) scrolltxt.grid(row=3,column=4, columnspan=1, rowspan=1, sticky=E+N+S) play = Button(self, text='Play', command=self.playUrl, bg='gray', fg='black') play.grid(row=1,column=4,padx=4,sticky=E+W)
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 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 PiScreen(tkinter.Frame): def __init__(self, master: 'tkinter.Tk'): global client, status, theme # host = '192.168.1.120' host = 'localhost' if sys.platform.startswith('linux'): host = 'localhost' client.connect(host, 6600) tkinter.Frame.__init__(self, master, padx=0, pady=0) self.pack() self.place(height=240, width=320, x=0, y=0) status = client.status() self.volume = int(status["volume"]) self.screen_data = { "1": [ "QUEUE", "PLAYLISTS", "LIBRARY", "SETUP", "CLEAR PLAYLIST", "RANDOM " + status['random'], "REPEAT " + status['repeat'], "SINGLE " + status['single'], "CONSUME " + status['consume'] ], "1.1": { "ACTION": "QUEUE" }, "1.2": { "ACTION": "PLAYLISTS" }, "1.3": ["ARTISTS", "ALBUMS", "GENRES"], "1.3.1": { "ACTION": "ARTISTS" }, "1.3.2": { "ACTION": "ALBUMS" }, "1.3.3": { "ACTION": "GENRES" }, "1.4": ["UPDATE LIBRARY", "THEMES"], "1.4.1": { "ACTION": "UPDATE_LIBRARY" }, "1.4.2": { "ACTION": "THEMES" }, "1.5": { "ACTION": "CLEAR" }, "1.6": { "ACTION": "RANDOM" }, "1.7": { "ACTION": "REPEAT" }, "1.8": { "ACTION": "SINGLE" }, "1.9": { "ACTION": "CONSUME" } } self.screen_format = {"1.Q": "SONG", "1.P": "PLAYLIST"} self.current_song_var = tkinter.StringVar() self.footer_text_var = tkinter.StringVar() # Screens self.playerScreen = Canvas(self, width=320, height=240, bg=theme['PLAYER']['background'], borderwidth=0, highlightthickness=0) self.menuScreen = Frame(self, width=320, height=240, bg="white") self.menuScreen.place(height=240, width=320, x=0, y=0) # Menu Screen items self.headerFrame = Frame(self.menuScreen, width=320, height=20, bg=theme['HEADER']['background']) self.headerFrame.pack(side=tkinter.TOP, fill=X) self.currentSongLabel = Label(self.headerFrame, font=(theme['HEADER']['font'], 12, 'bold'), bg=theme['HEADER']['background'], foreground=theme['HEADER']['foreground'], textvariable=self.current_song_var, justify=tkinter.LEFT, anchor=tkinter.W) self.currentSongLabel.place(x=0, y=0, width=300, height=20, anchor=tkinter.NW) self.volumeLabel = Label(self.headerFrame, font=(theme['HEADER']['font'], 10, 'bold'), bg=theme['HEADER']['background'], foreground=theme['HEADER']['foreground'], text='') self.volumeLabel.place(x=300, y=0, anchor=tkinter.NW) self.mainFrame = Frame(self.menuScreen, width=320, height=200) self.mainFrame.pack(side=tkinter.TOP, fill=Y) self.listbox = Listbox(self.mainFrame, selectmode=tkinter.SINGLE, font=(theme['MAIN']['font'], 11), bg=theme['MAIN']['background'], fg=theme['MAIN']['foreground'], height=10, activestyle="none", borderwidth=0, highlightthickness=0, selectbackground=theme['MAIN']['selected'], selectforeground=theme['MAIN']['foreground']) self.listbox.bind("<Key>", self.handle_keys) self.listbox.configure(width=320, height=11) self.listbox.pack(side=tkinter.TOP, expand=1, ipadx=0, ipady=0, padx=0, pady=0) self.listbox.focus_set() self.footer = Label(self.menuScreen, textvariable=self.footer_text_var, font=(theme['FOOTER']['font'], 10, 'bold'), bg=theme['FOOTER']['background'], foreground=theme['FOOTER']['foreground'], justify=tkinter.LEFT, anchor=tkinter.W) self.footer.configure(width=320, height=1) self.footer.pack(side=tkinter.BOTTOM) self.focus_set() self.bind("<Key>", self.handle_keys) self.screen = "1" self.show_screen() self.tick() def tick(self): global awayCount, keyMode, footerMessage, footerMessageCount self.update_header() if keyMode != 'PLAYER': awayCount += 1 if awayCount > 120: awayCount = 0 self.screen = '' self.show_screen() else: awayCount = 0 if footerMessage == self.footer_text_var.get(): footerMessageCount += 1 if footerMessageCount > 8: footerMessageCount = 0 self.footer_text_var.set("") else: footerMessage = self.footer_text_var.get() footerMessageCount = 0 self.after(800, self.tick) def update_header(self): global status, keyMode, songChanged, currentSong, songName, songTicker, minTickerLength, songTickerCount status = client.status() self.volume = int(status["volume"]) self.volumeLabel.configure(text=status["volume"]) if status["state"] == "play": currentSong = client.currentsong() song = currentSong["artist"] + " - " + currentSong["title"] if songName != song: songChanged = True songName = song if keyMode != 'PLAYER': # song changed, refresh ui if len(songName) >= minTickerLength: songTicker = True songTickerCount = -1 else: songTicker = False songTickerCount = 0 if keyMode != 'PLAYER': if songTicker: songTickerCount += 1 if songTickerCount == len(songName) + 5: songTickerCount = 0 song = songName + " " new_song = song[songTickerCount:] + song[:songTickerCount] self.current_song_var.set(new_song) elif keyMode == 'PLAYER': self.show_player() else: if songName != '': self.current_song_var.set('') songName = '' songChanged = True if keyMode == 'PLAYER': self.show_player() def show_screen(self): global keyMode if self.screen == '': keyMode = 'PLAYER' self.menuScreen.place_forget() self.playerScreen.place(height=240, width=320, x=0, y=0) self.show_player() self.update() self.screen = '1' return self.listbox.delete(0, self.listbox.size() - 1) format_name = "string" if self.screen in self.screen_format: format_name = self.screen_format[self.screen] if isinstance(self.screen_data[self.screen], list): for item in self.screen_data[self.screen]: if format_name == "string": if not item: self.listbox.insert(tkinter.END, "") else: self.listbox.insert(tkinter.END, item[:36]) if format_name == "SONG": songname = '' if 'artist' in item: songname = item['artist'][:18] songname += " - " if 'title' in item: max = 36 - len(songname) songname += item['title'][:max] self.listbox.insert(tkinter.END, songname) if format_name == "PLAYLIST": playlist_name = '' if isinstance(item, str): playlist_name = item else: playlist_name = item['playlist'] self.listbox.insert(tkinter.END, playlist_name) self.listbox.select_set(0) # This only sets focus on the first item. self.listbox.event_generate("<<ListboxSelect>>") self.update() return def show_player(self): global image, bg, songChanged, volumeChanged if songChanged or image is None: if sys.platform.startswith('linux'): process = subprocess.Popen( "./coverart.sh", shell=True, stdout=subprocess.PIPE).stdout.read() else: process = "./icons/ic_album_white_48dp.png" image = ImageTk.PhotoImage( Image.open(process).resize((136, 136), Image.ANTIALIAS)) if bg is None: process = "./icons/bg.png" if 'img_background' in theme['PLAYER']: process = theme['PLAYER']['img_background'] bg = ImageTk.PhotoImage( Image.open(process).resize((320, 240), Image.ANTIALIAS)) if icon_random is None: self.load_icons() if status["state"] == "play": if songChanged: self.playerScreen.delete(tkinter.ALL) self.playerScreen.create_image(160, 120, image=bg) self.playerScreen.create_rectangle( 10, 10, 150, 150, fill=theme['PLAYER']['foreground']) self.playerScreen.create_image(80, 80, image=image) self.playerScreen.create_image(178, 132, image=icon_random) self.playerScreen.create_image(224, 132, image=icon_repeat) self.playerScreen.create_image(270, 132, image=icon_single) self.playerScreen.create_rectangle( 298, 146, 308, 92, fill=theme['PLAYER']['background'], outline=theme['PLAYER']['foreground'], width=1) self.playerScreen.create_line( 303, 144, 303, 144 - int(self.volume / 2), fill=theme['PLAYER']['foreground'], width=7) self.playerScreen.create_text( 10, 160, text=currentSong['artist'], anchor=tkinter.NW, fill=theme['PLAYER']['foreground'], font=(theme['PLAYER']['font'], 14, 'bold')) self.playerScreen.create_text( 10, 185, text=currentSong['title'], anchor=tkinter.NW, fill=theme['PLAYER']['foreground'], font=(theme['PLAYER']['font'], 12, 'bold')) self.playerScreen.create_text( 10, 210, text=currentSong['album'], anchor=tkinter.NW, fill=theme['PLAYER']['foreground'], font=(theme['PLAYER']['font'], 10, 'bold')) else: time = str(status['time']).split(":") played = int((float(time[0]) / float(time[1])) * 320) if played < 3: # bug self.playerScreen.create_rectangle( 0, 236, 320, 240, fill=theme['PLAYER']['background']) self.playerScreen.create_rectangle( 0, 236, played, 240, fill=theme['PLAYER']['foreground']) if volumeChanged: volumeChanged = False self.playerScreen.create_rectangle( 298, 146, 308, 92, fill=theme['PLAYER']['background'], outline=theme['PLAYER']['foreground'], width=1) self.playerScreen.create_line( 303, 144, 303, 144 - int(self.volume / 2), fill=theme['PLAYER']['foreground'], width=7) else: # Blank Screen self.playerScreen.delete(tkinter.ALL) self.playerScreen.create_image(160, 120, image=bg) self.playerScreen.create_text( 20, 20, text=theme['PLAYER']['default_message'], anchor=tkinter.NW, fill=theme['PLAYER']['foreground'], font=(theme['PLAYER']['font'], 20, 'bold')) songChanged = False return def handle_keys(self, event): global config, client, selectedAlbum, selectedArtist, selectedGenre global keyMode, textEntry, textBackAction, textSaveAction, awayCount, theme_name global albums, artists, queue, songs, playlists, status, genres, songChanged, volumeChanged awayCount = 0 keycode = str(event.keycode) # self.footer_text_var.set(str("Key Pressed : "+keycode)) if keyMode == 'PLAYER' and keycode != config["PISCREEN_KEYS"]["vol_up"] \ and keycode != config["PISCREEN_KEYS"]["vol_down"] \ and keycode != config["PISCREEN_KEYS"]["play"] \ and keycode != config["PISCREEN_KEYS"]["next"] \ and keycode != config["PISCREEN_KEYS"]["prev"] \ and keycode != config["PISCREEN_KEYS"]["power"] \ and keycode != config["PISCREEN_KEYS"]["left"]: keyMode = 'MENU' self.playerScreen.place_forget() self.menuScreen.place(height=240, width=320, x=0, y=0) self.show_screen() self.update() return if keyMode == 'TEXT': if keycode == config["PISCREEN_KEYS"]["back"]: # back keyMode = 'MENU' self.run_command(textBackAction) if keycode == config["PISCREEN_KEYS"]["ok"]: # ok keyMode = 'MENU' self.run_command(textSaveAction) if event.keysym in '0123456789-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ': textEntry += event.keysym self.footer_text_var.set(str("Entry : " + textEntry)) return # self.footer.configure(text=str('Key Pressed ' + str(event.keycode))) if keycode == config["PISCREEN_KEYS"]["menu"]: if self.screen == "1.P": selection = int(self.listbox.curselection()[0]) + 1 if selection > 1: self.footer_text_var.set( str("Press 1 + OK to Delete Playlist")) keyMode = 'TEXT' textBackAction = "PLAYLISTS" textSaveAction = "DELETE_PLAYLIST" if self.screen == "1.Q": self.footer_text_var.set(str("Press OK to remove Song")) keyMode = 'TEXT' textBackAction = "QUEUE" textSaveAction = "DELETE_SONG" return if keycode == config["PISCREEN_KEYS"]["down"]: # down if self.listbox.size() > 0: selection = int(self.listbox.curselection()[0]) count = self.listbox.size() if selection < (count - 1): self.listbox.select_clear(selection) self.listbox.selection_set(selection + 1) self.listbox.event_generate("<<ListboxSelect>>") return if keycode == config["PISCREEN_KEYS"]["up"]: # up if self.listbox.size() > 0: selection = int(self.listbox.curselection()[0]) if selection > 0: self.listbox.select_clear(selection) self.listbox.selection_set(selection - 1) self.listbox.event_generate("<<ListboxSelect>>") return if keycode == config["PISCREEN_KEYS"]["left"] or keycode == config[ "PISCREEN_KEYS"]["back"]: # left or escape if self.screen != "1": menu = self.screen.rsplit(".", maxsplit=1) new_screen = menu[0] self.screen = new_screen self.show_screen() else: self.screen = '' songChanged = True self.show_screen() return if keycode == config["PISCREEN_KEYS"]["right"] or keycode == config[ "PISCREEN_KEYS"]["ok"]: # right or return if self.listbox.size() > 0: selection = int(self.listbox.curselection()[0]) + 1 new_screen = self.screen + "." + str(selection) if new_screen in self.screen_data: if type(self.screen_data[new_screen]) is list: self.screen = new_screen self.show_screen() else: self.run_command( self.screen_data[new_screen]["ACTION"]) else: if str(new_screen).startswith("1.Q."): menu = new_screen.rsplit(".", maxsplit=1) client.playid(int(queue[int(menu[1]) - 1]["id"])) return if str(new_screen).startswith("1.P."): menu = new_screen.rsplit(".", maxsplit=1) if menu[1] == "1": keyMode = 'TEXT' textBackAction = 'PLAYLISTS' textSaveAction = 'SAVE_PLAYLIST' textEntry = '' self.footer_text_var.set( 'Back to Cancel, Ok to Save') else: playlist = playlists[int(menu[1]) - 1]['playlist'] client.clear() client.load(playlist) client.play() return if str(new_screen).startswith("1.3.A"): if new_screen.count(".") == 3: menu = new_screen.rsplit(".", maxsplit=1) selectedArtist = artists[int(menu[1]) - 1] albums = [] albums = client.list("album", selectedArtist) albums[:0] = ["Add All"] self.footer_text_var.set("SELECTED Artist " + selectedArtist) self.screen = new_screen self.screen_data[new_screen] = albums self.show_screen() return elif new_screen.count(".") == 4: menu = new_screen.rsplit(".", maxsplit=1) if menu[1] == "1": # add all client.findadd("artist", selectedArtist) self.footer_text_var.set("Added All for " + selectedArtist) self.screen = menu[0].rsplit(".", maxsplit=1)[0] self.show_screen() else: selectedAlbum = albums[int(menu[1]) - 1] songs = client.list("title", "album", selectedAlbum, "artist", selectedArtist) songs[:0] = ["Add All"] self.screen = new_screen self.screen_data[new_screen] = songs self.show_screen() self.footer_text_var.set("Album Selected " + selectedAlbum) return elif new_screen.count(".") == 5: menu = new_screen.rsplit(".", maxsplit=1) if menu[1] == "1": # add all client.findadd("album", selectedAlbum, "artist", selectedArtist) self.footer_text_var.set("Added All for " + selectedAlbum + "/" + selectedArtist) self.screen = menu[0].rsplit(".", maxsplit=1)[0] self.show_screen() else: selected_song = songs[int(menu[1]) - 1] client.findadd("title", selected_song, "album", selectedAlbum, "artist", selectedArtist) self.footer_text_var.set("Added " + selected_song + "/" + selectedAlbum + "/" + selectedArtist) return if str(new_screen).startswith("1.3.B"): menu = new_screen.rsplit(".", maxsplit=1) if new_screen.count(".") == 3: selectedAlbum = albums[int(menu[1]) - 1] songs = client.list("title", "album", selectedAlbum) songs[:0] = ["Add All"] self.screen = new_screen self.screen_data[new_screen] = songs self.show_screen() self.footer_text_var.set("Album Selected " + selectedAlbum) if new_screen.count(".") == 4: if menu[1] == "1": # add all client.findadd("album", selectedAlbum) self.footer_text_var.set( "Added All for album " + selectedAlbum) self.screen = menu[0].rsplit(".", maxsplit=1)[0] self.show_screen() else: selected_song = songs[int(menu[1]) - 1] client.findadd("title", selected_song, "album", selectedAlbum) self.footer_text_var.set("Added " + selected_song + "/" + selectedAlbum) return if str(new_screen).startswith("1.3.C"): menu = new_screen.rsplit(".", maxsplit=1) if new_screen.count(".") == 3: selectedGenre = genres[int(menu[1]) - 1] songs = client.list("title", "genre", selectedGenre) self.screen = new_screen self.screen_data[new_screen] = songs self.show_screen() self.footer_text_var.set("Genre Selected " + selectedAlbum) if new_screen.count(".") == 4: selected_song = songs[int(menu[1]) - 1] client.findadd("title", selected_song, "genre", selectedGenre) self.footer_text_var.set("Added " + selected_song + selectedGenre) return if str(new_screen).startswith("1.4.T"): menu = new_screen.rsplit(".", maxsplit=1) theme_name = themes[int(menu[1]) - 1] self.footer_text_var.set("Applying Theme " + theme_name) self.apply_theme() return if keycode == config["PISCREEN_KEYS"]["vol_up"]: if self.volume < 100: self.volume += 1 client.setvol(self.volume) volumeChanged = True self.footer_text_var.set("Volume Up") else: self.footer_text_var.set("Volume Max!!") return if keycode == config["PISCREEN_KEYS"]["vol_down"]: if self.volume > 0: self.volume -= 1 client.setvol(self.volume) volumeChanged = True self.footer_text_var.set("Volume Down") else: self.footer_text_var.set("Volume Zero!!") return if keycode == config["PISCREEN_KEYS"]["play"]: if status["state"] == "play": client.pause() self.footer_text_var.set("Paused") else: client.play() self.footer_text_var.set("Playing") return if keycode == config["PISCREEN_KEYS"]["next"]: client.next() self.footer_text_var.set("Next Song") return if keycode == config["PISCREEN_KEYS"]["prev"]: client.previous() self.footer_text_var.set("Previous Song") return if keycode == config["PISCREEN_KEYS"]["home"]: self.screen = '' self.show_screen() return if keycode == config["PISCREEN_KEYS"]["power"]: if sys.platform.startswith('linux'): call("sudo nohup shutdown -h now", shell=True) else: self.footer_text_var.set("Can't PowerOff from remote") return self.footer_text_var.set("UNKNOWN " + keycode) def run_command(self, action): global client, keyMode, textEntry, status global albums, artists, queue, songs, playlists, genres, themes if action == "QUEUE": local_queue = client.playlistinfo() queue.clear() for item in local_queue: queue.append(item) self.screen = "1.Q" self.screen_data["1.Q"] = queue self.footer_text_var.set("Right to play Song, Menu to delete") self.show_screen() elif action == "PLAYLISTS": playlists = client.listplaylists() playlists[:0] = ["SAVE PLAYLIST"] self.screen = "1.P" self.screen_data["1.P"] = playlists self.footer_text_var.set("Right to play Playlist, Menu to delete") self.show_screen() elif action == "ARTISTS": artists = client.list("artist") self.screen = "1.3.A" self.screen_data["1.3.A"] = artists self.show_screen() elif action == "ALBUMS": albums = client.list("album") self.screen = "1.3.B" self.screen_data["1.3.B"] = albums self.show_screen() elif action == "GENRES": genres = client.list("genre") self.screen = "1.3.C" self.screen_data["1.3.C"] = genres self.show_screen() elif action == "UPDATE_LIBRARY": self.footer_text_var.set("Updating library") client.update() elif action == "THEMES": self.footer_text_var.set("Select Theme") themes = ["default", "foofighters", "light"] self.screen = "1.4.T" self.screen_data["1.4.T"] = themes self.show_screen() elif action == "SAVE_PLAYLIST": keyMode = 'MENU' found = False if textEntry == '': self.footer_text_var.set("Name Empty!!") return for playlist in playlists: if isinstance( playlist, str) is False and textEntry == playlist['playlist']: found = True if found: client.rm(textEntry) client.save(textEntry) else: client.save(textEntry) self.footer_text_var.set("Saved Playlist " + textEntry) textEntry = '' self.run_command("PLAYLISTS") elif action == "DELETE_PLAYLIST": keyMode = 'MENU' if textEntry == '1': selection = int(self.listbox.curselection()[0]) client.rm(playlists[selection]['playlist']) textEntry = '' self.run_command("PLAYLISTS") elif action == "DELETE_SONG": keyMode = 'MENU' client.delete(int(self.listbox.curselection()[0])) textEntry = '' self.run_command("QUEUE") elif action == "CLEAR": self.footer_text_var.set("Clearing Queue") client.clear() elif action == "RANDOM": if status['random'] == '0': client.random('1') else: client.random('0') status = client.status() self.screen_data['1'][5] = "RANDOM " + status['random'] self.update_random() self.show_screen() elif action == "REPEAT": if status['repeat'] == '0': client.repeat('1') else: client.repeat('0') status = client.status() self.screen_data['1'][6] = "REPEAT " + status['repeat'] self.update_repeat() self.show_screen() elif action == "SINGLE": if status['single'] == '0': client.single('1') else: client.single('0') status = client.status() self.screen_data['1'][7] = "SINGLE " + status['single'] self.update_single() self.show_screen() elif action == "CONSUME": if status['consume'] == '0': client.consume('1') else: client.consume('0') status = client.status() self.screen_data['1'][8] = "CONSUME " + status['consume'] self.show_screen() self.update() return def load_icons(self): self.update_random() self.update_repeat() self.update_single() def update_random(self): global status, theme, icon_random fgcolor = ImageColor.getrgb(theme['PLAYER']['foreground']) bgcolor = ImageColor.getrgb(theme['PLAYER']['background']) fgcolor += (255, ) bgcolor += (255, ) icon_random = Image.open('./icons/ic_shuffle_white_36dp.png') if icon_random.mode != 'RGBA': icon_random = icon_random.convert('RGBA') data = list(icon_random.getdata()) newData = list() for pixel in data: if pixel[3] != 0: if status['random'] == '1': newData.append(fgcolor) else: newData.append(bgcolor) else: newData.append(pixel) icon_random.putdata(newData) icon_random = ImageTk.PhotoImage( icon_random.resize((36, 36), Image.ANTIALIAS)) def update_single(self): global status, theme, icon_single fgcolor = ImageColor.getrgb(theme['PLAYER']['foreground']) bgcolor = ImageColor.getrgb(theme['PLAYER']['background']) fgcolor += (255, ) bgcolor += (255, ) icon_single = Image.open('./icons/ic_repeat_one_white_36dp.png') if icon_single.mode != 'RGBA': icon_single = icon_single.convert('RGBA') data = list(icon_single.getdata()) newData = list() for pixel in data: if pixel[3] != 0: if status['single'] == '1': newData.append(fgcolor) else: newData.append(bgcolor) else: newData.append(pixel) icon_single.putdata(newData) icon_single = ImageTk.PhotoImage( icon_single.resize((36, 36), Image.ANTIALIAS)) def update_repeat(self): global status, theme, icon_repeat fgcolor = ImageColor.getrgb(theme['PLAYER']['foreground']) bgcolor = ImageColor.getrgb(theme['PLAYER']['background']) fgcolor += (255, ) bgcolor += (255, ) icon_repeat = Image.open('./icons/ic_repeat_white_36dp.png') if icon_repeat.mode != 'RGBA': icon_repeat = icon_repeat.convert('RGBA') data = list(icon_repeat.getdata()) newData = list() for pixel in data: if pixel[3] != 0: if status['repeat'] == '1': newData.append(fgcolor) else: newData.append(bgcolor) else: newData.append(pixel) icon_repeat.putdata(newData) icon_repeat = ImageTk.PhotoImage( icon_repeat.resize((36, 36), Image.ANTIALIAS)) def apply_theme(self): global theme_name, theme, config, bg my_file = Path('./theme/' + theme_name + '/theme.ini') if my_file.is_file(): theme = configparser.ConfigParser() theme.read('./theme/' + theme_name + '/theme.ini') # player related settings bg = None self.playerScreen.configure(bg=theme['PLAYER']['background']) self.load_icons() # menu related settings self.headerFrame.configure(bg=theme['HEADER']['background']) self.currentSongLabel.configure( font=(theme['HEADER']['font'], 12, 'bold'), bg=theme['HEADER']['background'], foreground=theme['HEADER']['foreground']) self.volumeLabel.configure( font=(theme['HEADER']['font'], 10, 'bold'), bg=theme['HEADER']['background'], foreground=theme['HEADER']['foreground']) self.listbox.configure( font=(theme['MAIN']['font'], 11), bg=theme['MAIN']['background'], fg=theme['MAIN']['foreground'], selectbackground=theme['MAIN']['selected'], selectforeground=theme['MAIN']['foreground']) self.footer.configure(font=(theme['FOOTER']['font'], 10, 'bold'), bg=theme['FOOTER']['background'], foreground=theme['FOOTER']['foreground']) # write theme to config.ini config["THEME"]["theme"] = theme_name with open('config.ini', 'w') as configfile: config.write(configfile) else: self.footer_text_var.set("Theme Not Found") theme_name = config["THEME"]["theme"]