diff --git a/src/Components/ComboBox.py b/src/Components/ComboBox.py new file mode 100644 index 0000000..5286df2 --- /dev/null +++ b/src/Components/ComboBox.py @@ -0,0 +1,52 @@ +from tkinter import Toplevel, ttk, StringVar, PhotoImage +from typing import Callable + + +class ComboBox(ttk.Frame): + def __init__(self: object, parent: object, values: list, default_value: int = 0, image: PhotoImage = None, command: Callable = None) -> None: + super().__init__(parent) + self.parent: object = parent + self.values: list = values + self.command: Callable = command + self.value: StringVar = StringVar(value=values[default_value]) + ttk.Button(self, image=image, textvariable=self.value, style='third.TButton', + compound='right', command=self.toggle_panel).pack(anchor='c') + self.init_window() + + def init_window(self: object): + self.window: Toplevel = Toplevel(self.parent) + self.window.withdraw() + self.window.overrideredirect(True) + self.window.bind('', lambda _: self.hide()) + self.window.protocol('WM_DELETE_WINDOW', lambda _: self.hide()) + for option in self.values: + self.add_option(option) + + def add_option(self: object, option: str) -> None: + ttk.Button(self.window, text=option, command=lambda: self.select_option( + option), style='third.TButton').pack(anchor='c', fill='x') + + def toggle_panel(self: object) -> None: + self.window.deiconify() + # get mouse position + mouse_pos: tuple = self.parent.winfo_pointerxy() + # get button class + button: ttk.Button = self.parent.winfo_containing( + mouse_pos[0], mouse_pos[1]) + if button: + button_position: tuple = ( + button.winfo_rootx(), button.winfo_rooty()) + self.window.geometry( + f'{button.winfo_width()}x{len(self.values) * 38}+{button_position[0]}+{button_position[1] + 38}') + else: + self.window.geometry('') + self.window.geometry(f'+{mouse_pos[0]}+{mouse_pos[1]}') + self.window.focus_set() + + def select_option(self: object, option: str) -> None: + self.value.set(option) + self.window.withdraw() + self.command(option) + + def hide(self: object) -> None: + self.window.after(10, self.window.withdraw) diff --git a/src/Components/OLDDebugger.py b/src/Components/OLDDebugger.py new file mode 100644 index 0000000..7dc5ca8 --- /dev/null +++ b/src/Components/OLDDebugger.py @@ -0,0 +1,474 @@ +from tkinter import Toplevel, Label, Button, ttk, Frame, Canvas, Scrollbar, Entry, Tk, PhotoImage +from typing import ClassVar +from traceback import format_exc + + +class Debugger: + def __init__(self, parent) -> None: + self.parent = parent + del parent + self.main_window: ClassVar = Toplevel(self.parent) + self.disallowed_windows: list = [self.main_window.winfo_id()] + # hide window + self.main_window.withdraw() + # window style + self.main_window.configure(background='#212121') + self.main_theme: ClassVar = ttk.Style() + self.main_theme.theme_use('clam') + self.main_theme.layout('debugger.TEntry', [('Entry.padding', {'children': [ + ('Entry.textarea', {'sticky': 'nswe'})], 'sticky': 'nswe'})]) + self.main_theme.configure('debugger.TEntry', background='#111', foreground='#fff', + fieldbackground='#111', selectforeground='#fff', selectbackground='#333') + self.main_theme.configure('debugger.small.TButton', background='#111', relief='flat', font=( + 'Consolas', 9), foreground='#fff') + self.main_theme.map('debugger.small.TButton', background=[ + ('pressed', '!disabled', '#111'), ('active', '#151515')]) + self.main_theme.configure('debugger.TButton', background='#111', relief='flat', font=( + 'Consolas', 12), foreground='#fff') + self.main_theme.map('debugger.TButton', background=[ + ('pressed', '!disabled', '#111'), ('active', '#151515')]) + self.main_theme.configure('debugger.Vertical.TScrollbar', gripcount=0, relief='flat', background='#333', + darkcolor='#111', lightcolor='#111', troughcolor='#111', bordercolor='#111', arrowcolor='#333') + self.main_theme.layout('debugger.Vertical.TScrollbar', [('Vertical.Scrollbar.trough', { + 'children': [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})], 'sticky': 'ns'})]) + self.main_theme.map('debugger.Vertical.TScrollbar', background=[( + 'pressed', '!disabled', '#313131'), ('disabled', '#111'), ('active', '#313131'), ('!active', '#333')]) + # window attributes + self.main_window.attributes("-topmost", True) + self.main_window.title(f'DEBUGGING: {self.parent.title()}') + # self.main_window.geometry('665x800') + self.main_window.minsize(665, 500) + self.main_window.protocol('WM_DELETE_WINDOW', self.close_debugger) + # variables + self.widget: ClassVar = None + self.highlighted_elements: dict = {} + self.inspecting: bool = False + self.allow_self_debug = False + self.blank_photo: ClassVar = PhotoImage(height=16, width=16) + self.blank_photo.blank() + self.main_window.iconphoto(False, self.blank_photo) + # content + top_frame: ClassVar = Frame( + self.main_window, background=self.main_window['background']) + # inspect button + self.inspect_button: ClassVar = ttk.Button( + top_frame, text='INSPECT ELEMENT', takefocus=False, style='debugger.TButton', command=self.toggle_inspect) + self.inspect_button.pack(side='left', padx=(10, 0), pady=10) + self.inspect_next_button: ClassVar = ttk.Button( + top_frame, text='INSPECT NEXT', takefocus=False, style='debugger.TButton', command=self.inspect_next) + self.inspect_next_button.state(['disabled']) + self.inspect_next_button.pack(side='left', padx=(10, 0), pady=10) + self.widgets_label: ClassVar = Label( + top_frame, text=f'{len(self.get_all_widgets(self.parent))} WIDGETS', background='#111', foreground='#fff', font=('Consolas', 12)) + self.widgets_label.pack(side='left', padx=( + 10, 0), pady=10, ipady=5, ipadx=5) + self.refresh_button: ClassVar = ttk.Button( + top_frame, text='REFRESH', takefocus=False, style='debugger.TButton', command=lambda: self.inspect_widget(self.widget)) + self.refresh_button.state(['disabled']) + self.refresh_button.pack(side='left', padx=(10, 0)) + self.mode_label: ClassVar = Label( + top_frame, text='NORMAL', background='#111', foreground='#fff', font=('Consolas', 12)) + self.mode_label.pack(side='left', padx=10, ipady=5, ipadx=5) + top_frame.pack(side='top', fill='x') + mid_frame: ClassVar = Frame( + self.main_window, background=self.main_window['background']) + widget_frame: ClassVar = Frame(mid_frame, background='#333') + Label(widget_frame, text='WIDGET CLASS, NAME', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + self.widget_name: ClassVar = Label( + widget_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.widget_name.pack(side='top', fill='x', padx=5, pady=5) + Label(widget_frame, text='WIDGET DIMENTIONS', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + self.widget_dimensions: ClassVar = Label( + widget_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.widget_dimensions.pack(side='top', fill='x', padx=5, pady=5) + Label(widget_frame, text='WIDGET MANAGER', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + self.widget_manager: ClassVar = Label( + widget_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.widget_manager.pack(side='top', fill='x', padx=5, pady=5) + Label(widget_frame, text='MANAGER CONFIG', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + self.manager_config: ClassVar = Label( + widget_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.manager_config.pack(side='top', fill='x', padx=5, pady=5) + Label(widget_frame, text='WIDGET PARENT', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + parent_frame: ClassVar = Frame( + widget_frame, background=widget_frame['background']) + self.widget_perent: ClassVar = Label( + parent_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.widget_perent.pack(side='left', fill='x', expand=True) + self.inspect_perent: ClassVar = ttk.Button(parent_frame, text='INSPECT', takefocus=False, style='debugger.small.TButton', + command=lambda: self.inspect_widget(self.widget._nametowidget(self.widget.winfo_parent()))) + self.inspect_perent.state(['disabled']) + self.inspect_perent.pack(side='left', fill='x', padx=5) + parent_frame.pack(side='top', fill='x', padx=5, pady=(5, 0)) + Label(widget_frame, text='WIDGET BINDINGS', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + self.widget_bindings: ClassVar = Label( + widget_frame, text='', background='#111', foreground='#fff', anchor='w', font=('Consolas', 12)) + self.widget_bindings.pack(side='top', fill='x', padx=5, pady=5) + Label(widget_frame, text='WIDGET PROPERTIES', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + properties_frame: ClassVar = Frame( + widget_frame, background=widget_frame['background']) + properties_text: ClassVar = Frame( + properties_frame, background=properties_frame['background']) + Label(properties_text, text='WIDGET TEXT:', background='#111', foreground='#fff', + anchor='w', font=('Consolas', 12)).pack(side='left', fill='x') + self.entry: ClassVar = ttk.Entry( + properties_text, style='debugger.TEntry', font=('Consolas', 12)) + self.entry.state(['disabled']) + self.entry.pack(side='left', fill='x', ipady=2, + expand=True, padx=(5, 0)) + self.apply_button: ClassVar = ttk.Button( + properties_text, text='APPLY', takefocus=False, style='debugger.small.TButton', command=self.apply_changes) + self.apply_button.state(['disabled']) + self.apply_button.pack(side='left', fill='x', padx=5) + properties_text.pack(side='top', fill='x', pady=(0, 5)) + properties_image: ClassVar = Frame( + properties_frame, background=properties_frame['background']) + Label(properties_image, text='WIDGET IMG:', background='#111', foreground='#fff', + anchor='w', font=('Consolas', 12)).pack(side='left', fill='x') + self.widget_image: ClassVar = ttk.Button( + properties_image, text='OPEN IMAGE', takefocus=False, style='debugger.small.TButton', command=self.open_image) + self.widget_image.state(['disabled']) + self.widget_image.pack(side='left', fill='x', padx=5) + properties_image.pack(side='top', fill='x', pady=(0, 5)) + properties_function: ClassVar = Frame( + properties_frame, background=properties_frame['background']) + Label(properties_function, text='WIDGET FUNCTION:', background='#111', + foreground='#fff', anchor='w', font=('Consolas', 12)).pack(side='left', fill='x') + self.widget_function: ClassVar = ttk.Button( + properties_function, text='CALL FUNCTION', takefocus=False, style='debugger.small.TButton', command=self.call_function) + self.widget_function.state(['disabled']) + self.widget_function.pack(side='left', fill='x', padx=5) + properties_function.pack(side='top', fill='x') + properties_frame.pack(side='top', fill='x', padx=5, pady=(5, 0)) + Label(widget_frame, text='WIDGET CHILDRENS', background=widget_frame['background'], foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=5, pady=(5, 0)) + canvas_frame: ClassVar = Frame( + widget_frame, background=widget_frame['background']) + scrollbar: ClassVar = ttk.Scrollbar( + canvas_frame, style='debugger.Vertical.TScrollbar') + self.canvas: ClassVar = Canvas( + canvas_frame, borderwidth=0, highlightthickness=0, background='#111', yscrollcommand=scrollbar.set) + scrollbar.configure(command=self.canvas.yview) + self.canvas_cards: ClassVar = Frame( + self.canvas, background=self.canvas['background']) + self.canvas_cards.bind('', lambda _: self.canvas.configure( + scrollregion=self.canvas.bbox('all'))) + self.canvas_window: ClassVar = self.canvas.create_window( + (0, 0), window=self.canvas_cards, anchor='nw') + self.canvas.bind('', lambda _: self.canvas.itemconfigure( + self.canvas_window, width=self.canvas.winfo_width(), height=len(self.canvas_cards.winfo_children()) * 51)) + self.canvas.pack(side='left', fill='both', + expand=True, padx=10, pady=10) + scrollbar.pack(side='right', fill='y', pady=10, padx=(0, 10)) + canvas_frame.pack(side='top', fill='both', expand=True) + widget_frame.pack(side='top', fill='both', expand=True, padx=10) + mid_frame.pack(side='top', fill='both', expand=True) + Label(self.main_window, text='PyDeb BY MATEUSZ PERCZAK (Łosiek)', background='#111', foreground='#fff', font=( + 'Consolas', 12), anchor='w').pack(side='top', fill='x', padx=10, pady=(10, 0)) + # show window + self.main_window.bind('', self.on_mouse) + self.entry.bind('', self.entry_diff) + self.main_window.bind('', lambda _: self.close_debugger()) + self.main_window.after(100, self.init_img_window) + self.main_window.after(150, lambda: self.inspect_widget(self.parent)) + self.check_bind_collisions() + self.main_window.deiconify() + self.show_window() + self.main_window.mainloop() + + def check_bind_collisions(self) -> None: + parent_binds: tuple = self.parent.bind() + if parent_binds: + message: str = '' + if '' in parent_binds: + message += ', ' + else: + self.parent.bind( + '', lambda _: self.load_properties()) + if '' in parent_binds: + message += ', ' + self.inspect_button.state(['disabled']) + if '' in parent_binds: + message += ' ' + self.inspect_button.state(['disabled']) + if '' in parent_binds: + message += ' ' + else: + self.main_window.bind('', self.switch_mode) + if message: + print( + f'Warning it appears that your root window uses binds {message}that may collide with the debugger.', f'To minimalize conflicts {message}will be disabled!') + del message, parent_binds + else: + self.parent.bind('', lambda _: self.load_properties()) + self.main_window.bind('', self.finish_inspection) + self.main_window.bind('', self.switch_mode) + + def toggle_inspect(self) -> None: + if self.inspecting: + self.stop_inspecting() + else: + self.start_inspecting() + + def stop_inspecting(self) -> None: + self.inspecting = False + self.parent.unbind('') + self.parent.unbind('') + self.inspect_button['text'] = 'INSPECT ELEMENT' + + def start_inspecting(self) -> None: + self.inspecting = True + self.parent.bind('', self.while_inspection) + self.parent.bind('', self.finish_inspection) + self.inspect_button['text'] = 'INSPECTING ...' + + def while_inspection(self, _) -> None: + position: tuple = self.parent.winfo_pointerxy() + widget: ClassVar = self.parent.winfo_containing( + position[0], position[1]) + if widget: + pass + if self.widget != widget: + self.widget = widget + self.unhighlight_elements() + self.load_properties() + if not self.widget in self.highlighted_elements: + self.highlight_element(self.widget, '#3498db') + del position, widget + + def load_properties(self) -> None: + if self.widget: + self.refresh_button.state(['!disabled']) + self.widget_name['text'] = f'{self.widget.winfo_class()} ({self.widget.winfo_name()})' + self.widget_dimensions['text'] = f'X: {self.widget.winfo_x()} Y: {self.widget.winfo_y()} WIDTH: {self.widget.winfo_width()} HEIGHT: {self.widget.winfo_height()}' + self.widget_manager['text'] = self.widget.winfo_manager().upper() + self.manager_config['text'] = '' + widget_config: dict = {} + if self.widget_manager['text'] == 'PACK': + widget_config = self.widget.pack_info() + elif self.widget_manager['text'] == 'PLACE': + widget_config = self.widget.place_info() + elif self.widget_manager['text'] == 'GRID': + widget_config = self.widget.grid_info() + if widget_config: + for key in widget_config: + if key == 'in' or not widget_config[key] or widget_config[key] == 'none': + continue + self.manager_config['text'] += f'{key}: {widget_config[key]} ' + self.manager_config['text'] = self.manager_config['text'].upper( + ) + del widget_config + self.widget_perent['text'] = self.widget.winfo_parent() + self.widget_bindings['text'] = self.widget.bind() + if self.widget.winfo_class() in ('Button', 'TButton', 'Label', 'TLabel', 'Radiobutton'): + self.entry.state(['!disabled']) + self.entry.delete(0, 'end') + self.entry.insert(0, self.widget['text']) + self.widget_image.state(['disabled']) + if 'image' in self.widget.config() and self.widget['image']: + self.widget_image.state(['!disabled']) + self.img_label['image'] = self.widget['image'] + else: + self.img_label['image'] = self.blank_photo + if 'command' in self.widget.config(): + self.widget_function.state(['!disabled']) + elif self.widget.winfo_class() in ('Entry', 'TEntry'): + self.entry.state(['!disabled']) + self.entry.delete(0, 'end') + self.entry.insert(0, self.widget.get()) + self.widget_image.state(['disabled']) + self.widget_function.state(['disabled']) + else: + self.entry.delete(0, 'end') + self.entry.state(['disabled']) + self.widget_image.state(['disabled']) + self.widget_function.state(['disabled']) + self.img_label['image'] = self.blank_photo + self.widget_function.state(['disabled']) + if self.widget != self.parent: + self.inspect_perent.state(['!disabled']) + if len(self.widget._nametowidget(self.widget.winfo_parent()).winfo_children()) > 1: + self.inspect_next_button.state(['!disabled']) + else: + self.inspect_next_button.state(['disabled']) + else: + self.inspect_next_button.state(['disabled']) + self.inspect_perent.state(['disabled']) + else: + self.refresh_button.state(['disabled']) + + def init_img_window(self) -> None: + self.img_window: ClassVar = Toplevel() + self.disallowed_windows.append(self.img_window.winfo_id()) + self.img_window.withdraw() + self.img_window.title('') + self.img_window.minsize(150, 150) + self.img_window.attributes("-topmost", True) + self.img_window.iconphoto(False, self.blank_photo) + self.img_label: ClassVar = Label(self.img_window, background='#111') + self.img_label.place(x=0, y=0, relwidth=1, relheight=1) + self.img_window.protocol( + 'WM_DELETE_WINDOW', lambda: self.img_window.withdraw()) + self.img_window.mainloop() + + def open_image(self) -> None: + if not self.img_window.winfo_ismapped(): + self.img_window.deiconify() + + def call_function(self) -> None: + self.unhighlight_elements() + self.widget.invoke() + self.highlight_element(self.widget, '#f1c40f') + + def inspect_next(self) -> None: + if self.widget: + parent_childrens: list = self.widget._nametowidget( + self.widget.winfo_parent()).winfo_children() + for window in self.disallowed_windows: + for child in parent_childrens: + if window == child.winfo_id(): + parent_childrens.remove(child) + widget_index: int = parent_childrens.index(self.widget) + if len(parent_childrens) > widget_index + 1: + self.inspect_widget(parent_childrens[widget_index + 1]) + else: + self.inspect_widget(parent_childrens[0]) + del parent_childrens, widget_index + + def switch_mode(self, _) -> None: + self.allow_self_debug = not self.allow_self_debug + if self.allow_self_debug: + self.mode_label['text'] = 'EXTENDED' + else: + self.mode_label['text'] = 'NORMAL' + self.load_properties() + self.load_widget_childrens() + self.update_canvas() + + def inspect_widget(self, widget) -> None: + if widget: + self.widget = widget + self.load_widget_childrens() + self.update_canvas() + self.unhighlight_elements() + self.load_properties() + if not self.widget in self.highlighted_elements: + self.highlight_element(self.widget, '#3498db') + + def finish_inspection(self, _) -> None: + self.stop_inspecting() + self.load_properties() + self.load_widget_childrens() + self.update_canvas() + + def load_widget_childrens(self) -> None: + for widget in self.get_all_widgets(self.canvas_cards): + widget.destroy() + for widget in self.widget.winfo_children(): + self.add_child(widget) + + def update_canvas(self) -> None: + self.canvas.itemconfigure(self.canvas_window, width=self.canvas.winfo_width( + ), height=len(self.widget.winfo_children()) * 51) + self.canvas.yview_moveto(0) + + def add_child(self, widget) -> None: + if self.allow_self_debug or not widget.winfo_id() in self.disallowed_windows: + child_frame: ClassVar = Frame( + self.canvas_cards, background=self.main_window['background']) + Label(child_frame, text=f'{widget.winfo_class()}', background='#333', foreground='#fff', font=( + 'Consolas', 12), ).pack(side='left', anchor='center', fill='y') + Label(child_frame, text=f'POS_X {widget.winfo_x()} POS_Y {widget.winfo_y()} WIDTH {widget.winfo_width()}PX HEIGHT {widget.winfo_height()}PX', + background='#111', foreground='#fff', font=('Consolas', 12)).pack(side='left', anchor='center', padx=(10, 0)) + ttk.Button(child_frame, text='INSPECT', style='debugger.TButton', command=lambda: self.inspect_widget( + widget)).pack(side='right', anchor='center', padx=10) + child_frame.pack(side='top', fill='x', + pady=(5, 0), ipady=5, padx=5) + + def entry_diff(self, _) -> None: + if self.widget: + if self.widget.winfo_class() in ('Button', 'TButton', 'Label', 'TLabel', 'Radiobutton'): + if self.widget['text'] == self.entry.get(): + self.apply_button.state(['disabled']) + else: + self.apply_button.state(['!disabled']) + elif self.widget.winfo_class() in ('Entry', 'TEntry'): + if self.widget.get() == self.entry.get(): + self.apply_button.state(['disabled']) + else: + self.apply_button.state(['!disabled']) + + def apply_changes(self) -> None: + if self.widget: + if self.widget.winfo_class() in ('Button', 'TButton', 'Label', 'TLabel', 'Radiobutton'): + self.widget['text'] = self.entry.get() + elif self.widget.winfo_class() in ('Entry', 'TEntry'): + self.widget.delete(0, 'end') + self.widget.insert(0, self.entry.get()) + self.apply_button.state(['disabled']) + + def highlight_element(self, widget, color) -> None: + if widget: + if not widget in self.highlighted_elements: + if 'background' in widget.config(): + self.highlighted_elements[widget] = widget['background'] + widget['background'] = color + if widget.winfo_class() in ('Button', 'Radiobutton'): + widget['state'] = 'disabled' + else: + self.highlighted_elements[widget] = '' + self.widget.state(['disabled']) + + def unhighlight_elements(self) -> None: + widgets_to_remove: list = [] + for widget in self.highlighted_elements: + if not widget: + widgets_to_remove.append(widget) + continue + if 'background' in widget.config(): + widget['background'] = self.highlighted_elements[widget] + if widget.winfo_class() in ('Button', 'Radiobutton'): + widget['state'] = 'normal' + else: + widget.state(['!disabled']) + widgets_to_remove.append(widget) + for widget in widgets_to_remove: + self.highlighted_elements.pop(widget) + del widgets_to_remove + + def show_window(self) -> None: + if self.parent: + if not self.parent.winfo_ismapped(): + self.parent.deiconify() + + def get_all_widgets(self, widget) -> list: + try: + widget_list = widget.winfo_children() + for widget in widget_list: + if widget.winfo_children(): + widget_list.extend(widget.winfo_children()) + return widget_list + except Exception as _: + print(format_exc()) + + def on_mouse(self, event): + self.canvas.yview_scroll(int(-1 * (event.delta / 120)), 'units') + + def close_debugger(self) -> None: + try: + self.parent.unbind('') + self.parent.unbind('') + self.parent.unbind('') + self.unhighlight_elements() + self.img_window.destroy() + self.main_window.destroy() + self.main_window.quit() + except Exception as _: + print(format_exc()) diff --git a/src/Components/TkDeb/Components/Inspector.py b/src/Components/TkDeb/Components/Inspector.py new file mode 100644 index 0000000..039a318 --- /dev/null +++ b/src/Components/TkDeb/Components/Inspector.py @@ -0,0 +1,124 @@ +from tkinter import Event +from typing import Callable + + +class Inspector: + def __init__(self: object, parent: object) -> object: + # variables + self.parent: object = parent + self.widget: object = None + self.update_speed: int = 500 + self.run: bool = True + self.highlighted_widget: list = [] + self.ignore_widgets = ('TProgressbar', 'Progressbar') + # events + self.bindings: dict = {'start': [], + 'end': [], 'change': [], 'pointing': [], } + + parent.after(1000, lambda: self.monitor_widget(parent)) + + def start_inspecting(self: object) -> None: + self.__notify('start') + self.parent.bind('', self.__while_inspecting) + self.parent.bind('', self.__finish_inspecting) + + def stop_inspecting(self: object) -> None: + self.parent.unbind('') + self.parent.unbind('') + + def get_widget(self: object) -> object: + return self.widget + + def set_widget(self: object, widget: object) -> None: + self.widget = widget + self.__notify('change') + + def __while_inspecting(self: object, _: Event) -> None: + position: tuple = self.parent.winfo_pointerxy() + widget: object = self.parent.winfo_containing(position[0], position[1]) + if widget is not self.widget: + self.__unhighlight_widget(self.widget) + self.widget = widget + self.__highlight_widget(widget) + self.__notify('pointing') + + def __finish_inspecting(self: object, _: Event) -> None: + self.__notify('end') + self.parent.unbind('') + self.parent.unbind('') + self.__unhighlight_all() + + def get_children(self: object, widget: object) -> list: + children_list = widget.winfo_children() + for widget in children_list: + if widget.winfo_children(): + children_list.extend(widget.winfo_children()) + return children_list + + def bind(self: object, bind_type: str, methode: Callable) -> None: + self.bindings[bind_type].append(methode) + + def unbind(self: object, methode: Callable) -> None: + for key in self.bindings: + if methode in self.bindings[key]: + self.bindings[key].remove(methode) + + def __notify(self: object, notify_type: str) -> None: + for methode in self.bindings[notify_type]: + methode() + + def monitor_widget(self: object, widget: object) -> None: + widget.bind('', lambda _: self.__notify('change'), add='+') + self.parent.after(1000, self.__update) + + def __update(self: object) -> None: + if self.run: + self.__notify('change') + self.parent.after(self.update_speed, self.__update) + + def unbind_all(self: object) -> None: + self.run = False + self.parent.unbind('') + self.parent.unbind('') + + def __highlight_widget(self: object, widget: object) -> None: + widget_class: str = widget.winfo_class() + if widget_class not in self.highlighted_widget: + widget_config: dict = widget.config() + if 'state' in widget_config: + self.highlighted_widget.append(widget) + if widget_class[0] == 'T': + widget.state(['disabled']) + else: + widget['state'] = 'disabled' + + def __unhighlight_widget(self: object, widget: object) -> None: + widget_class: str = widget.winfo_class() + if widget in self.highlighted_widget: + widget_config: dict = widget.config() + if 'state' in widget_config: + self.highlighted_widget.remove(widget) + if widget_class[0] == 'T': + widget.state(['!disabled']) + else: + widget['state'] = 'normal' + + def __unhighlight_all(self: object) -> None: + for widget in self.highlighted_widget: + self.__unhighlight_widget(widget) + + def delete_current_widget(self: object, _: Event = None) -> None: + if self.widget and self.widget.winfo_class() not in ('Tk', 'Toplevel'): + widget_parent: object = self.parent._nametowidget( + self.widget.winfo_parent()) + parent_childrens: list = widget_parent.winfo_children() + child_index: int = parent_childrens.index(self.widget) + # delete widget + parent_childrens.remove(self.widget) + self.widget.destroy() + if parent_childrens: + # select next widget + self.widget = parent_childrens[child_index - 1] + else: + # select parent widget + self.widget = widget_parent diff --git a/src/Components/TkDeb/Components/Matcher.py b/src/Components/TkDeb/Components/Matcher.py new file mode 100644 index 0000000..4da11a4 --- /dev/null +++ b/src/Components/TkDeb/Components/Matcher.py @@ -0,0 +1,75 @@ +class Matcher: + def __init__(self: object) -> object: + self.properties: dict = { + 'Tk': {'Class': '', 'Name': '', 'Manager': '', 'Geometry': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': ''}, + 'Toplevel': {'Class': '', 'Name': '', 'Manager': '', 'Geometry': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': ''}, + 'TLabel': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Text': '', 'WrapLength': 'Infinite', 'Justify': 'Default', 'Image': 'Not specified', 'Compound': 'None', 'Style': 'Default'}, + 'TButton': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Text': '', 'Image': 'Not specified', 'Compound': 'None', 'Style': 'Default'}, + 'TFrame': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Childrens': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Style': 'Default'}, + 'TRadiobutton': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Text': '', 'Image': 'Not specified', 'Compound': 'None', 'Style': 'Default'}, + 'TScale': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Value': '', 'Style': 'Default'}, + 'TEntry': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Value': '', 'Style': 'Default'}, + 'TProgressbar': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Progress': ''}, + 'TCheckbutton': {'Class': '', 'Name': '', 'Manager': '', 'Manager Config': '', 'Parent': '', 'Position': '', 'Dimentions': '', 'Binds': '', 'State': '', 'Visible': '', 'Text': '', 'WrapLength': 'Infinite', 'Image': 'Not specified', 'Compound': 'None', 'Style': 'Default'} + + } + + def get_properties(self: object, widget: object) -> dict: + properties: dict = { + 'Class': widget.winfo_class(), + 'Name': f'{widget}', + 'Manager': widget.winfo_manager(), + 'Geometry': widget.winfo_geometry(), + 'Parent': widget.winfo_parent(), + 'Text': widget['text'] if widget.winfo_class() in ('TLabel', 'Label', 'TButton', 'Button', 'TRadiobutton') else '', + 'WrapLength': widget['wraplength'] if widget.winfo_class() in ('TLabel', 'Label') else '', + 'Position': f'X: {widget.winfo_x()} Y: {widget.winfo_y()}', + 'Dimentions': f'Width: {widget.winfo_width()} Height: {widget.winfo_height()}', + 'Binds': widget.bind(), + 'Manager Config': self.__get_manager_config(widget), + 'Value': widget.get() if widget.winfo_class() in ('TScale', 'TEntry') else '', + 'State': widget.state() if widget.winfo_class()[0] == 'T' else '', + 'Progress': widget['value'] if widget.winfo_class() in ('TProgressbar') else '', + 'Image': widget['image'] if widget.winfo_class() in ('TButton', 'TLabel', 'TRadiobutton') else '', + 'Visible': bool(widget.winfo_ismapped()), + 'Compound': f'{widget["compound"]}'.capitalize() if widget.winfo_class() in ('TLabel', 'TButton', 'TRadiobutton') else '', + 'Justify': widget['justify'] if widget.winfo_class() in ('TLabel') else '', + 'Style': widget['style'] if widget.winfo_class() not in ('Tk', 'Toplevel') and widget.winfo_class()[0] == 'T' else '', + 'Childrens': len(widget.winfo_children()), + } + return properties + + def match(self: object, widget: object) -> dict: + properties: dict = self.get_properties(widget) + widget_class: str = widget.winfo_class() + available_properties: dict = {} + if widget_class in self.properties: + for key in self.properties[widget_class]: + propertie: str = properties[key] + available_properties[key] = propertie if propertie else self.properties[widget_class][key] + return available_properties + else: + available_properties['WARNING'] = f' THIS IS A LEGACY OR UNKNOWN WIDGET' + available_properties[''] = ' PROPS BELOW =>' + + for key in widget.keys(): + available_properties[key.capitalize()] = f' {widget[key]}' + return available_properties + + def __get_manager_config(self: object, widget: object) -> str: + manager: str = widget.winfo_manager() + widget_config: dict = {} + if manager == 'pack': + widget_config = widget.pack_info() + elif manager == 'grid': + widget_config = widget.grid_info() + elif manager == 'place': + widget_config = widget.place_info() + config: str = '' + for key in widget_config: + if key == 'in' or not widget_config[key] or widget_config[key] in ('None', 'none', 0): + continue + config += f'{key}: {widget_config[key]} ' + if config: + return config + return 'Unknown' diff --git a/src/Components/TkDeb/Components/SystemTheme.py b/src/Components/TkDeb/Components/SystemTheme.py new file mode 100644 index 0000000..e6e1126 --- /dev/null +++ b/src/Components/TkDeb/Components/SystemTheme.py @@ -0,0 +1,5 @@ +from darkdetect import theme + + +def get_theme() -> str: + return theme() diff --git a/src/Components/TkDeb/Components/View.py b/src/Components/TkDeb/Components/View.py new file mode 100644 index 0000000..48e9f05 --- /dev/null +++ b/src/Components/TkDeb/Components/View.py @@ -0,0 +1,122 @@ +from tkinter import Tk, ttk, PhotoImage +from typing import Callable +from ..Components.SystemTheme import get_theme +from os.path import join + + +class Layout: + def __init__(self: object, parent: Tk, absolute_path: str) -> object: + # pass parent object + self.parent = parent + # init theme object + self.parent.layout = ttk.Style(self.parent) + # abs path + self.abs_path = absolute_path + + self.parent.layout.layout('debugger.TButton', [('Button.padding', { + 'sticky': 'nswe', 'children': [('Button.label', {'sticky': 'nswe'})]})]) + # treeview + self.parent.layout.configure('debugger.Treeview', indent=25, rowheight=25, font=( + 'Catamaran', 10, 'bold')) + self.parent.layout.layout('debugger.Treeview', [('Treeview.treearea', { + 'sticky': 'nswe'})]) # Remove the borders + self.tree_open = PhotoImage( + file=join(self.abs_path, r'Resources\\plus.png')) + self.tree_close = PhotoImage( + file=join(self.abs_path, r'Resources\\minus.png')) + self.tree_empty = PhotoImage( + file=join(self.abs_path, r'Resources\\empty.png')) + self.parent.layout.element_create('Treeitem.indicator', + 'image', self.tree_open, ('user1', '!user2', + self.tree_close), ('user2', self.tree_empty), + sticky='w', width=20) + self.parent.layout.layout('Treeview.Item', + [('Treeitem.padding', + {'sticky': 'nswe', + 'children': [('Treeitem.indicator', {'side': 'left', 'sticky': ''}), + ('Treeitem.image', { + 'side': 'left', 'sticky': ''}), + ('Treeitem.text', { + 'side': 'left', 'sticky': ''}) + ]})] + ) + + # scrollbar + self.parent.layout.element_create( + 'debugger.Vertical.Scrollbar.trough', 'from', 'clam') + self.parent.layout.element_create( + 'debugger.Vertical.Scrollbar.thumb', + 'from', 'clam') + self.parent.layout.layout('debugger.Vertical.TScrollbar', [('debugger.Vertical.Scrollbar.trough', {'children': [ + ('debugger.Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'nswe'})], 'sticky': 'ns'})]) + + +class Theme: + def __init__(self: object, parent: Tk) -> None: + # pass parent object + self.parent = parent + self.colors: dict = {'Dark': ['#111', '#212121', '#333', '#fff'], 'Light': [ + '#fff', '#ecf0f1', '#ecf0f1', '#000']} + # get system theme + self.system_theme: str = get_theme() + self.colors['System'] = self.colors[self.system_theme] + # set default applied theme + self.applied_theme: str = 'Light' + # bindings + self.bindings: dict = {'changed': []} + + def apply(self: object, theme: str) -> None: + self.applied_theme = theme + # pass parent object + self.parent.configure(background=self.colors[theme][1]) + # frames + self.parent.layout.configure( + 'debugger.TFrame', background=self.colors[theme][0]) + self.parent.layout.configure( + 'debugger.dark.TFrame', background=self.colors[theme][1]) + # label + self.parent.layout.configure('debugger.TLabel', background=self.colors[theme][0], font=( + 'catamaran 12 bold'), foreground=self.colors[theme][3]) + # button + self.parent.layout.configure('debugger.TButton', background=self.colors[theme][0], font=( + 'catamaran 12 bold'), foreground=self.colors[theme][3], anchor='w', padding=4) + + self.parent.layout.map('debugger.TButton', background=[('pressed', '!disabled', self.colors[theme][1]), ( + 'active', self.colors[theme][1]), ('selected', self.colors[theme][1])]) + + # treewiev + self.parent.layout.map('debugger.Treeview', background=[ + ('selected', self.colors[theme][1])], foreground=[('selected', self.colors[theme][3])]) + + # scrollbar + self.parent.layout.configure('debugger.Vertical.TScrollbar', gripcount=0, background=self.colors[theme][2], darkcolor=self.colors[ + theme][0], lightcolor=self.colors[theme][0], troughcolor=self.colors[theme][0], bordercolor=self.colors[theme][0]) + + self.parent.layout.map('debugger.Vertical.TScrollbar', background=[('pressed', '!disabled', self.colors[theme][2]), ( + 'disabled', self.colors[theme][0]), ('active', self.colors[theme][2]), ('!active', self.colors[theme][2])]) + + # notify event + self.__notify('changed') + + def get_theme(self: object) -> str: + if self.applied_theme == 'System': + return self.system_theme + return self.applied_theme + + def get_internal_theme(self: object) -> str: + return self.applied_theme + + def get_colors(self: object, theme: str) -> list: + return self.colors[theme] + + def bind(self: object, bind_type: str, methode: Callable) -> None: + self.bindings[bind_type].append(methode) + + def unbind(self: object, methode: Callable) -> None: + for key in self.bindings: + if methode in self.bindings[key]: + self.bindings[key].remove(methode) + + def __notify(self: object, notify_type: str) -> None: + for methode in self.bindings[notify_type]: + methode() diff --git a/src/Components/TkDeb/Components/WidgetProperties.py b/src/Components/TkDeb/Components/WidgetProperties.py new file mode 100644 index 0000000..63b0277 --- /dev/null +++ b/src/Components/TkDeb/Components/WidgetProperties.py @@ -0,0 +1,32 @@ +from tkinter import ttk, Listbox +from .Matcher import Matcher + + +class WidgetProperties(ttk.Frame): + def __init__(self: ttk.Frame, parent: object) -> ttk.Frame: + super().__init__(parent, style='debugger.TFrame') + # variables + self.matcher: object = Matcher() + # ui + ttk.Label(self, text="Widget properties", style='debugger.TLabel').pack( + side='top', padx=10, fill='x', pady=(10, 5)) + + self.list_box: Listbox = Listbox( + self, bd=0, activestyle="none", takefocus=False, selectmode="SINGLE", highlightthickness=0, font=( + 'Catamaran', 11, 'bold'), selectbackground='#fff', selectforeground='#000') + self.list_box.pack(side='left', fill='both', expand=True, padx=10) + + # scrollbar + self.scrollbar: ttk.Scrollbar = ttk.Scrollbar( + self, orient='vertical', command=self.list_box.yview, style='debugger.Vertical.TScrollbar') + self.list_box.configure(yscrollcommand=self.scrollbar.set) + self.scrollbar.pack(side='left', fill='y') + + def load_properties(self: object, widget: object) -> None: + if widget: + scroll_pos = self.list_box.yview()[0] + self.list_box.delete(0, 'end') + properties: dict = self.matcher.match(widget) + for key in properties: + self.list_box.insert('end', f'{key}: {properties[key]}') + self.list_box.yview_moveto(scroll_pos) diff --git a/src/Components/TkDeb/Components/WidgetTree.py b/src/Components/TkDeb/Components/WidgetTree.py new file mode 100644 index 0000000..12079d1 --- /dev/null +++ b/src/Components/TkDeb/Components/WidgetTree.py @@ -0,0 +1,103 @@ +from tkinter import ttk, PhotoImage, Event +from os.path import join +from typing import Callable + + +class WidgetTree(ttk.Frame): + def __init__(self: ttk.Frame, parent: object, disallowed_windows: list, abs_path: str) -> ttk.Frame: + super().__init__(parent, style='debugger.TFrame') + # variables + self.disallowed_windows: list = disallowed_windows + self.target_tree: dict = {} + self.abs_path: str = abs_path + self.last_target: object = None + + self.bindings: dict = {'select': []} + # ui + + self.icons: dict = { + 'Tk': PhotoImage(file=join(self.abs_path, r'Resources\\app.png')), + 'TFrame': PhotoImage(file=join(self.abs_path, r'Resources\\frame.png')), + 'TButton': PhotoImage(file=join(self.abs_path, r'Resources\\button.png')), + 'TLabel': PhotoImage(file=join(self.abs_path, r'Resources\\label.png')), + 'TEntry': PhotoImage(file=join(self.abs_path, r'Resources\\entry.png')), + 'TRadiobutton': PhotoImage(file=join(self.abs_path, r'Resources\\radiobutton.png')), + 'TCombobox': PhotoImage(file=join(self.abs_path, r'Resources\\combobox.png')), + 'TScale': PhotoImage(file=join(self.abs_path, r'Resources\\scale.png')), + 'TProgressbar': PhotoImage(file=join(self.abs_path, r'Resources\\progress.png')), + 'TCheckbutton': PhotoImage(file=join(self.abs_path, r'Resources\\checkbox.png')), + 'Treeview': PhotoImage(file=join(self.abs_path, r'Resources\\tree.png')), + 'Unknown': PhotoImage(file=join(self.abs_path, r'Resources\\unknown.png')), + } + self.icons['Toplevel'] = self.icons['Tk'] + + ttk.Label(self, text='Widgets tree', style='debugger.TLabel').pack( + side='top', fill='x', padx=10, pady=(10, 5)) + mid_frame: ttk.Frame = ttk.Frame(self, style='debugger.TFrame') + # tree + self.tree_view: ttk.Treeview = ttk.Treeview( + mid_frame, style='debugger.Treeview', show="tree") + + # disable any interactoins with the tree + self.tree_view.bind('', 'break') + self.tree_view.bind('', + lambda _: self.__notify('select')) + # pack tree + self.tree_view.pack(side='left', fill='both', + expand=True, padx=10, pady=10) + # scrollbar + self.tree_scrollbar: ttk.Scrollbar = ttk.Scrollbar( + mid_frame, orient='vertical', command=self.tree_view.yview, style='debugger.Vertical.TScrollbar') + self.tree_view.configure(yscrollcommand=self.tree_scrollbar.set) + self.tree_scrollbar.pack(side='left', fill='y') + + mid_frame.pack(side='top', fill='both', expand=True) + + def generate_tree(self: ttk.Frame, target: object) -> None: + self.tree_view.delete(*self.tree_view.get_children()) + target_id: int = target.winfo_id() + self.tree_view.insert('', 'end', text=target.winfo_class(), + iid=target_id, open=False, image=self.icons['Tk'], value=target) + self.target_tree[target] = { + 'children': self.__find_children(target), 'name': target.winfo_class().upper(), 'id': target_id} + self.last_target = target + + def __find_children(self: ttk.Frame, widget: object) -> None: + childs: list = [] + for child in widget.winfo_children(): + if child in self.disallowed_windows: + continue + widget_class: str = child.winfo_class() + child_id: int = child.winfo_id() + self.tree_view.insert( + widget.winfo_id(), 'end', text=f"{widget_class}".upper(), iid=child_id, value=child, open=False, image=self.icons[widget_class if widget_class in self.icons else 'Unknown']) + childs.append(child) + self.target_tree[child] = {'children': self.__find_children( + child), 'name': widget_class, 'id': child_id} + + return childs + + def highlight_widget(self: ttk.Frame, widget: object) -> None: + if widget: + widget_id: str = widget.winfo_id() + if widget in self.target_tree: + self.tree_view.selection_set(widget_id) + self.tree_view.see(widget_id) + + def get_widget(self: ttk.Frame) -> object: + iid: str = self.tree_view.focus() + if iid: + widget = self.tree_view.item(iid)['values'][0] + return self.nametowidget(widget) + + def bind(self: object, bind_type: str, methode: Callable) -> None: + self.bindings[bind_type].append(methode) + + def unbind(self: object, methode: Callable) -> None: + for key in self.bindings: + if methode in self.bindings[key]: + self.bindings[key].remove(methode) + + def __notify(self: object, notify_type: str) -> None: + for methode in self.bindings[notify_type]: + methode(self.get_widget()) diff --git a/src/Components/TkDeb/Pages/InitPage.py b/src/Components/TkDeb/Pages/InitPage.py new file mode 100644 index 0000000..1d82de9 --- /dev/null +++ b/src/Components/TkDeb/Pages/InitPage.py @@ -0,0 +1,13 @@ +from tkinter import ttk, PhotoImage +from os.path import join + + +class InitPage(ttk.Frame): + def __init__(self: ttk.Frame, parent: object, props: dict) -> ttk.Frame: + super().__init__(parent, style='debugger.TFrame') + # variables + self.logo: PhotoImage = PhotoImage( + file=join(props['abs_path'], r'Resources\\icon.png')), + # ui + ttk.Label(self, image=self.logo, style='debugger.TLabel').place( + relx=.5, rely=0.5, anchor='c') diff --git a/src/Components/TkDeb/Pages/MainPage.py b/src/Components/TkDeb/Pages/MainPage.py new file mode 100644 index 0000000..914e14f --- /dev/null +++ b/src/Components/TkDeb/Pages/MainPage.py @@ -0,0 +1,127 @@ +from tkinter import ttk, PhotoImage, StringVar +from ..Components.WidgetTree import WidgetTree +from ..Components.WidgetProperties import WidgetProperties +from os.path import join + + +class MainPage(ttk.Frame): + def __init__(self: ttk.Frame, parent: object, props: dict) -> ttk.Frame: + super().__init__(parent, style='debugger.dark.TFrame') + # variables + self.parent: object = props['parent'] + self.disallowed_windows: list = props['disallowed_windows'] + self.inspecting: bool = False + self.inspector: object = props['inspector'] + self.abs_path = props['abs_path'] + self.pinned: bool = False + self.__move = props['move'] + self.num_of_widgets: int = len( + self.inspector.get_children(self.parent)) + # widgets counter + self.widgets_counter: StringVar = StringVar( + value=f"{self.num_of_widgets} ") + + # icons + + self.icons = { + 'inspect': [PhotoImage(file=join(self.abs_path, r'Resources\\start_inspection.png')), PhotoImage(file=join(self.abs_path, r'Resources\\end_inspection.png'))], + 'widgets': PhotoImage(file=join(self.abs_path, r'Resources\\widgets.png')), + 'pin': [PhotoImage(file=join(self.abs_path, r'Resources\\pin.png')), PhotoImage(file=join(self.abs_path, r'Resources\\unpin.png'))], + 'delete': PhotoImage(file=join(self.abs_path, r'Resources\\delete.png')), + } + + # ui + top_frame: ttk.Frame = ttk.Frame(self, style='debugger.dark.TFrame') + # inspect button + self.inspect_button: ttk.Button = ttk.Button( + top_frame, image=self.icons['inspect'][self.inspecting], command=self.toggle_inspection, style='debugger.TButton') + self.inspect_button.pack(side='left', padx=10) + # widgets label + ttk.Label( + top_frame, image=self.icons['widgets'], textvariable=self.widgets_counter, style='debugger.TLabel', compound='left').pack(side='left', padx=(0, 10), fill='y') + # pin bytton + self.pin_button: ttk.Button = ttk.Button( + top_frame, image=self.icons['pin'][self.pinned], command=self.pin_window, style='debugger.TButton') + self.pin_button.pack(side='left', padx=(0, 10)) + ttk.Button( + top_frame, image=self.icons['delete'], command=self.inspector.delete_current_widget, style='debugger.TButton').pack(side='left', padx=(0, 10)) + + top_frame.pack(side='top', fill='x', pady=10) + # middle frame + mid_frame: ttk.Frame = ttk.Frame(self, style='debugger.dark.TFrame') + # treeview + self.widget_tree: WidgetTree = WidgetTree( + mid_frame, self.disallowed_windows, self.abs_path) + # self.widget_tree.pack(side='left', fill='both', padx=10) + self.widget_tree.place(relx=0, y=0, relwidth=0.4, relheight=1) + # widget props + self.widget_props: WidgetProperties = WidgetProperties(mid_frame) + # self.widget_props.pack(side='left', fill='both', padx=(0, 10)) + self.widget_props.place(relx=.4, y=0, relwidth=0.6, relheight=1) + # pack mid frame + mid_frame.pack(side='top', fill='both', + expand=True, pady=(0, 10), padx=10) + + ttk.Label(self, text=' TkDeb by Mateusz Perczak', + style='debugger.TLabel').pack(side='left', fill='x', expand=True, pady=(0, 10), padx=10) + # generate tree + self.widget_tree.generate_tree(self.parent) + self.widget_tree.bind('select', self.select) + # bind events + self.inspector.bind('pointing', self.highlight_widget) + self.inspector.bind('end', self.end_inspection) + self.inspector.bind('change', self.update) + # select widget + self.select(self.parent) + + def highlight_widget(self: ttk.Frame) -> None: + widget: object = self.inspector.get_widget() + self.widget_tree.highlight_widget(widget) + self.widget_props.load_properties(widget) + + def toggle_inspection(self: ttk.Frame) -> None: + if self.inspecting: + self.end_inspection() + else: + self.prepare_inspection() + + def prepare_inspection(self: ttk.Frame) -> None: + self.inspecting = True + self.inspector.start_inspecting() + self.update_inspect_button() + + def end_inspection(self: ttk.Frame) -> None: + self.inspecting = False + self.update_inspect_button() + self.inspector.stop_inspecting() + self.widget_props.load_properties(self.inspector.get_widget()) + + def update_inspect_button(self: ttk.Frame) -> None: + self.inspect_button.config( + image=self.icons['inspect'][self.inspecting]) + + def pin_window(self: ttk.Frame) -> None: + self.pinned = not self.pinned + if self.pinned: + self.__move() + self.inspector.bind('change', self.__move) + else: + self.inspector.unbind(self.__move) + self.pin_button.config(image=self.icons['pin'][self.pinned]) + + def update(self: ttk.Frame) -> None: + num_of_widgets: int = len(self.inspector.get_children(self.parent)) + widget: object = self.inspector.get_widget() + if self.num_of_widgets != num_of_widgets: + self.num_of_widgets = num_of_widgets + self.widget_tree.generate_tree(self.parent) + self.widget_tree.highlight_widget(widget) + self.widgets_counter.set(f"{self.num_of_widgets} ") + if widget: + self.widget_props.load_properties(widget) + + def select(self: ttk.Frame, widget: object) -> None: + old_widget = self.inspector.get_widget() + if old_widget != widget: + self.inspector.widget = widget + self.widget_props.load_properties(widget) diff --git a/src/Components/TkDeb/Resources/app.png b/src/Components/TkDeb/Resources/app.png new file mode 100644 index 0000000..6b64ee8 Binary files /dev/null and b/src/Components/TkDeb/Resources/app.png differ diff --git a/src/Components/TkDeb/Resources/button.png b/src/Components/TkDeb/Resources/button.png new file mode 100644 index 0000000..a5e3812 Binary files /dev/null and b/src/Components/TkDeb/Resources/button.png differ diff --git a/src/Components/TkDeb/Resources/checkbox.png b/src/Components/TkDeb/Resources/checkbox.png new file mode 100644 index 0000000..812aea2 Binary files /dev/null and b/src/Components/TkDeb/Resources/checkbox.png differ diff --git a/src/Components/TkDeb/Resources/combobox.png b/src/Components/TkDeb/Resources/combobox.png new file mode 100644 index 0000000..ee9d515 Binary files /dev/null and b/src/Components/TkDeb/Resources/combobox.png differ diff --git a/src/Components/TkDeb/Resources/delete.png b/src/Components/TkDeb/Resources/delete.png new file mode 100644 index 0000000..9d140da Binary files /dev/null and b/src/Components/TkDeb/Resources/delete.png differ diff --git a/src/Components/TkDeb/Resources/empty.png b/src/Components/TkDeb/Resources/empty.png new file mode 100644 index 0000000..ca30bb1 Binary files /dev/null and b/src/Components/TkDeb/Resources/empty.png differ diff --git a/src/Components/TkDeb/Resources/end_inspection.png b/src/Components/TkDeb/Resources/end_inspection.png new file mode 100644 index 0000000..dd27972 Binary files /dev/null and b/src/Components/TkDeb/Resources/end_inspection.png differ diff --git a/src/Components/TkDeb/Resources/entry.png b/src/Components/TkDeb/Resources/entry.png new file mode 100644 index 0000000..4c7cfcc Binary files /dev/null and b/src/Components/TkDeb/Resources/entry.png differ diff --git a/src/Components/TkDeb/Resources/frame.png b/src/Components/TkDeb/Resources/frame.png new file mode 100644 index 0000000..f6079e4 Binary files /dev/null and b/src/Components/TkDeb/Resources/frame.png differ diff --git a/src/Components/TkDeb/Resources/icon.ico b/src/Components/TkDeb/Resources/icon.ico new file mode 100644 index 0000000..9aa7e30 Binary files /dev/null and b/src/Components/TkDeb/Resources/icon.ico differ diff --git a/src/Components/TkDeb/Resources/icon.png b/src/Components/TkDeb/Resources/icon.png new file mode 100644 index 0000000..c33fbe3 Binary files /dev/null and b/src/Components/TkDeb/Resources/icon.png differ diff --git a/src/Components/TkDeb/Resources/label.png b/src/Components/TkDeb/Resources/label.png new file mode 100644 index 0000000..1a1c23c Binary files /dev/null and b/src/Components/TkDeb/Resources/label.png differ diff --git a/src/Components/TkDeb/Resources/minus.png b/src/Components/TkDeb/Resources/minus.png new file mode 100644 index 0000000..411e1c6 Binary files /dev/null and b/src/Components/TkDeb/Resources/minus.png differ diff --git a/src/Components/TkDeb/Resources/pin.png b/src/Components/TkDeb/Resources/pin.png new file mode 100644 index 0000000..1b8610a Binary files /dev/null and b/src/Components/TkDeb/Resources/pin.png differ diff --git a/src/Components/TkDeb/Resources/plus.png b/src/Components/TkDeb/Resources/plus.png new file mode 100644 index 0000000..c78b2f9 Binary files /dev/null and b/src/Components/TkDeb/Resources/plus.png differ diff --git a/src/Components/TkDeb/Resources/progress.png b/src/Components/TkDeb/Resources/progress.png new file mode 100644 index 0000000..15df1a2 Binary files /dev/null and b/src/Components/TkDeb/Resources/progress.png differ diff --git a/src/Components/TkDeb/Resources/radiobutton.png b/src/Components/TkDeb/Resources/radiobutton.png new file mode 100644 index 0000000..0425e9b Binary files /dev/null and b/src/Components/TkDeb/Resources/radiobutton.png differ diff --git a/src/Components/TkDeb/Resources/scale.png b/src/Components/TkDeb/Resources/scale.png new file mode 100644 index 0000000..27db192 Binary files /dev/null and b/src/Components/TkDeb/Resources/scale.png differ diff --git a/src/Components/TkDeb/Resources/start_inspection.png b/src/Components/TkDeb/Resources/start_inspection.png new file mode 100644 index 0000000..ffcbb37 Binary files /dev/null and b/src/Components/TkDeb/Resources/start_inspection.png differ diff --git a/src/Components/TkDeb/Resources/tree.png b/src/Components/TkDeb/Resources/tree.png new file mode 100644 index 0000000..81d097e Binary files /dev/null and b/src/Components/TkDeb/Resources/tree.png differ diff --git a/src/Components/TkDeb/Resources/unknown.png b/src/Components/TkDeb/Resources/unknown.png new file mode 100644 index 0000000..d8576cc Binary files /dev/null and b/src/Components/TkDeb/Resources/unknown.png differ diff --git a/src/Components/TkDeb/Resources/unpin.png b/src/Components/TkDeb/Resources/unpin.png new file mode 100644 index 0000000..2a2d20e Binary files /dev/null and b/src/Components/TkDeb/Resources/unpin.png differ diff --git a/src/Components/TkDeb/Resources/widgets.png b/src/Components/TkDeb/Resources/widgets.png new file mode 100644 index 0000000..6cd576e Binary files /dev/null and b/src/Components/TkDeb/Resources/widgets.png differ diff --git a/src/Components/TkDeb/TkDeb.py b/src/Components/TkDeb/TkDeb.py new file mode 100644 index 0000000..3513d25 --- /dev/null +++ b/src/Components/TkDeb/TkDeb.py @@ -0,0 +1,64 @@ +from tkinter import Toplevel +from .Pages.InitPage import InitPage +from .Pages.MainPage import MainPage +from .Components.View import Layout, Theme +from .Components.Inspector import Inspector +from os.path import dirname, join + + +class Debugger(Toplevel): + def __init__(self: Toplevel, parent: object) -> None: + super().__init__(parent, name='debugger') + # variables + self.parent: object = parent + self.disallowed_windows: list = [self] + self.abs_path = dirname(__file__) + # load layout + self.custom_layout = Layout(self, self.abs_path) + # load theme + self.theme = Theme(self) + self.theme.apply('Light') + # inspector object + self.inspector: Inspector = Inspector(parent) + + # window properties + self.title(f"Debugging: {self.parent.title()}") + self.configure(background='#222') + self.attributes("-topmost", True) + self.minsize(665, 500) + self.protocol('WM_DELETE_WINDOW', self.__exit) + self.iconbitmap(join(self.abs_path, r'Resources\\icon.ico')) + # binds + self.parent.unbind('') + self.bind('', lambda _: self.__exit()) + self.bind('', self.inspector.delete_current_widget) + # display init page + self.init_page: InitPage = InitPage( + self, props={'abs_path': self.abs_path}) + self.init_page.pack(fill='both', expand=True) + # init ui + self.after(150, self.__init) + + def __init(self: Toplevel) -> None: + + # load parent properties + self.main_page: MainPage = MainPage( + self, props={'parent': self.parent, 'disallowed_windows': self.disallowed_windows, 'inspector': self.inspector, 'abs_path': self.abs_path, 'move': self.__move}) + self.init_page.destroy() + self.main_page.pack(fill='both', expand=True) + + def __exit(self: Toplevel) -> None: + self.inspector.unbind_all() + self.destroy() + + def __move(self: Toplevel) -> None: + # variables + parent_x: int = self.parent.winfo_x() + self_width: int = self.winfo_width() + # move window + if (parent_x - self_width) <= 0: + self.geometry( + f'{self_width}x{self.winfo_height()}+{(parent_x + self.parent.winfo_width()) + 5}+{self.parent.winfo_y()}') + else: + self.geometry( + f'{self_width}x{self.winfo_height()}+{(parent_x - self_width) - 5}+{self.parent.winfo_y()}') diff --git a/src/Components/TkDeb/__init__.py b/src/Components/TkDeb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/Resources/Dumps/sounder_dump.txt b/src/Resources/Dumps/sounder_dump.txt index ad1ef6f..f3c9bb7 100644 --- a/src/Resources/Dumps/sounder_dump.txt +++ b/src/Resources/Dumps/sounder_dump.txt @@ -1,120 +1,23 @@ -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller.spell(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' -ERROR:root:'Speller' object has no attribute 'spell' -Traceback (most recent call last): - File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1423, in sort_panels - if SequenceMatcher(a=token, b=self.speller(search_word)).quick_ratio() >= self.settings['search_compensation']: -AttributeError: 'Speller' object has no attribute 'spell' +ERROR:root:'NoneType' object has no attribute 'info' +Traceback (most recent call last): + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1208, in scan_folders + self.new_song(song) + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1580, in new_song + self.cache_song(song) + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1574, in cache_song + self.songs_cache[song] = {'title': song_title, 'artist': song_artist, 'album': album, 'album_art': album_art, 'length': song_metadata.info.length, +AttributeError: 'NoneType' object has no attribute 'info' +ERROR:root:'NoneType' object has no attribute 'info' +Traceback (most recent call last): + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1209, in scan_folders + self.new_song(song) + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1583, in new_song + self.cache_song(song) + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1577, in cache_song + self.songs_cache[song] = {'title': song_title, 'artist': song_artist, 'album': album, 'album_art': album_art, 'length': song_metadata.info.length, +AttributeError: 'NoneType' object has no attribute 'info' +ERROR:root:Position not implemented for music type +Traceback (most recent call last): + File "e:/Python/New Projects/Sounder5/Sounder5.py", line 1720, in mixer_play + mixer.music.play(start=start) +pygame.error: Position not implemented for music type diff --git a/src/Setup/autocorrect/data/en.tar.gz b/src/Setup/autocorrect/data/en.tar.gz new file mode 100644 index 0000000..2ca983d Binary files /dev/null and b/src/Setup/autocorrect/data/en.tar.gz differ diff --git a/src/Sounder5.py b/src/Sounder5.py index 26a6369..d53b960 100644 --- a/src/Sounder5.py +++ b/src/Sounder5.py @@ -21,6 +21,7 @@ from mutagen.mp3 import MP3 from mutagen.flac import FLAC from mutagen.oggvorbis import OggVorbis + from mutagen.wave import WAVE from difflib import SequenceMatcher from pygame import mixer from win10toast import ToastNotifier @@ -126,7 +127,7 @@ def init_settings(self: Tk) -> None: default_settings: dict = {'search_correction': True, 'played_percent': 2, 'menu_position': 'left', 'search_compensation': 0.7, 'delete_missing': False, 'follow': 1, 'crossfade': 100, 'shuffle': False, 'start_playback': False, 'playlist': 'Library', 'repeat': 'None', 'buffer': 'Normal', 'last_song': '', 'volume': 0.5, 'sort_by': 'A-Z', 'scan_subfolders': False, 'geometry': '800x500', 'wheel_acceleration': 1.0, 'updates': True, 'folders': [], 'use_system_theme': True, 'theme': 'Light', 'page': 'Library', 'playlists': {'Favorites': {'Name': 'Favorites', 'Songs': []}}} self.settings: dict = {} - self.version: tuple = ('0.8.6', '130522') + self.version: tuple = ('0.8.7', '270522') # load settings if isfile(r'Resources\\Settings\\Settings.json'): with open(r'Resources\\Settings\\Settings.json', 'r') as data: @@ -1191,7 +1192,7 @@ def scan_folders(self: Tk) -> None: for folder in folders: if exists(folder): for file in listdir(folder): - if file.endswith(('.mp3', '.flac', '.ogg')) and file not in self.library: + if file.endswith(('.mp3', '.flac', '.ogg', '.wav')) and file not in self.library: self.library.append( abspath(join(folder, file))) elif isdir(join(folder, file)) and file not in ('System Volume Information', '$RECYCLE.BIN') and file not in folders: @@ -1570,6 +1571,9 @@ def cache_song(self: Tk, song: str) -> None: song_artist = song_artist.join( song_metadata['comment']).replace(',', ' ').replace('&', '') search_tokens += f'{song_artist} ' + elif song.endswith('.wav'): + song_metadata = WAVE(song) + # cache data self.songs_cache[song] = {'title': song_title, 'artist': song_artist, 'album': album, 'album_art': album_art, 'length': song_metadata.info.length, 'kbps': song_metadata.info.bitrate, 'genre': genre, 'search_tokens': search_tokens.lower().split(), 'plays': 0} @@ -1700,7 +1704,7 @@ def init_mixer(self: Tk) -> None: size = 2048 if self.settings['buffer'] == 'Fast': size = 512 - mixer.pre_init(44800, -16, 2, size) + mixer.pre_init(192000, -16, 2, size) mixer.init() except Exception as err_obj: self.log(err_obj) diff --git a/updates/changelog.txt b/updates/changelog.txt index d72c601..018afd2 100644 --- a/updates/changelog.txt +++ b/updates/changelog.txt @@ -1,4 +1,2 @@ Changelog: -+Added spelling correction setting --Removed unused icons -~Now updater uses compiled updates that take less space! \ No newline at end of file ++Added support for wav files \ No newline at end of file diff --git a/updates/hash.txt b/updates/hash.txt index 1bad461..f7e464f 100644 --- a/updates/hash.txt +++ b/updates/hash.txt @@ -1 +1 @@ -e181184fce88bbcee52658c1d45c65f3fc7f800ff1e174d48a842209590143bd \ No newline at end of file +c99eba6f342213c7df2beb79bb8ff1b49141537a9592503e87ce0afd5d0af00d \ No newline at end of file diff --git a/updates/package.zip b/updates/package.zip index 398dc29..e47124d 100644 Binary files a/updates/package.zip and b/updates/package.zip differ diff --git a/updates/version.txt b/updates/version.txt index 120f532..35864a9 100644 --- a/updates/version.txt +++ b/updates/version.txt @@ -1 +1 @@ -0.8.6 \ No newline at end of file +0.8.7 \ No newline at end of file