class categorylistFrame(tk.Frame): def __init__(self, parent, controller, framework, packages): #list of packages to be displayed by this frame self.packages = packages self.parent = parent self.controller = controller #Frame manager self.framework = framework #**Scheduler self.appstore_handler = Store_handler #Tool to get installed package data etc self.current_search = None self.selected = False self.sort_type = None self.listbox_list = [] tk.Frame.__init__(self, parent, background=style.w, border=0, highlightthickness=0) threader.do_async(self.build_listframe) def build_listframe(self): columnwidth = 0.33 * (self.winfo_width() - style.updated_column_width) self.body_frame = tk.Frame(self, background=style.color_1) self.body_frame.place(relwidth=1, relheight=1) self.scrollbar = tk.Scrollbar(self.body_frame, troughcolor=style.color_1, bg=style.color_2) self.scrollbar.place(relheight=1, width=style.scrollbarwidth, relx=1, x=-style.scrollbarwidth, height=-(style.listbox_footer_height + style.listbox_footer_height), y=+style.listbox_footer_height) self.listbox_frame = tk.Frame(self.body_frame, background=style.color_2) self.listbox_frame.place(relwidth=1, relheight=1, height=-style.listbox_footer_height, width=-style.scrollbarwidth) self.scaling_listboxes_frame = tk.Frame(self.listbox_frame, background=style.color_2) self.scaling_listboxes_frame.place(relwidth=1, relheight=1, width=-style.updated_column_width) self.scaling_listboxes_frame_header = tk.Frame( self.scaling_listboxes_frame, background=style.color_2) self.scaling_listboxes_frame_header.place( relwidth=1, height=style.listbox_header_height) self.scaling_listboxes_frame_body = tk.Frame( self.scaling_listboxes_frame, background=style.color_2) self.scaling_listboxes_frame_body.place( relwidth=1, relheight=1, y=style.listbox_header_height, height=-style.listbox_header_height) self.package_listbox = ThemedListbox(self.scaling_listboxes_frame_body, background=style.color_2, font=style.smallboldtext, borderwidth=1, foreground=style.w) self.package_listbox.place(relx=0, relwidth=0.33333, relheight=1) self.package_listbox_label = ThemedLabel( self.scaling_listboxes_frame_header, text="Package", background=style.color_1, font=style.mediumboldtext) self.package_listbox_label.place(relx=0, relwidth=0.33333, relheight=1) self.title_listbox = ThemedListbox(self.scaling_listboxes_frame_body, background=style.color_2, font=style.smalltext, borderwidth=1) self.title_listbox.place(relx=0.33333, relwidth=0.33333, relheight=1) self.title_listbox_label = ThemedLabel( self.scaling_listboxes_frame_header, text="Title", background=style.color_1, font=style.mediumboldtext) self.title_listbox_label.place(relx=0.33333, relwidth=0.33333, relheight=1) self.author_listbox = ThemedListbox(self.scaling_listboxes_frame_body, background=style.color_2, font=style.smalltext, borderwidth=1) self.author_listbox.place(relx=0.66666, relwidth=0.33333, relheight=1) self.author_listbox_label = ThemedLabel( self.scaling_listboxes_frame_header, text="Author", background=style.color_1, font=style.mediumboldtext) self.author_listbox_label.place(relx=0.66666, relwidth=0.33333, relheight=1) self.updated_listbox = ThemedListbox(self.listbox_frame, background=style.color_2, font=style.smalltext, borderwidth=1) self.updated_listbox.place(relx=1, width=style.updated_column_width, x=-style.updated_column_width, relheight=1, y=style.listbox_header_height, height=-style.listbox_header_height) self.updated_listbox_label = ThemedLabel(self.listbox_frame, text="Updated", background=style.color_1, font=style.mediumboldtext) self.updated_listbox_label.place(relx=1, width=style.updated_column_width, x=-style.updated_column_width, height=style.listbox_header_height) self.listbox_list = [ self.package_listbox, self.title_listbox, self.author_listbox, self.updated_listbox ] self.listbox_frame_footer = tk.Frame(self, background=style.color_1) self.listbox_frame_footer.place(relwidth=1, rely=1, y=-style.listbox_footer_height, height=style.listbox_footer_height) self.installed_label = ThemedLabel(self.listbox_frame_footer, text="Installed - ◆", background=style.color_1, foreground="green", anchor="center") self.installed_label.place(relx=0, relwidth=0.333, relheight=1) self.hasupdate_label = ThemedLabel(self.listbox_frame_footer, text="Update Available - ◆", background=style.color_1, foreground="yellow", anchor="center") self.hasupdate_label.place(relx=0.333, relwidth=0.333, relheight=1) self.notinstalled_label = ThemedLabel(self.listbox_frame_footer, text="Not installed - ◆", background=style.color_1, foreground="white", anchor="center") self.notinstalled_label.place(relx=0.666, relwidth=0.333, relheight=1) self.package_listbox.configure({"selectbackground": style.lg}) self.package_listbox.configure({"selectmode": "single"}) self.package_listbox.bind("<<ListboxSelect>>", self.on_listbox_selection) self.scrollbar.config(command=self.on_scroll_bar) self.package_listbox.config(yscrollcommand=self.scrollbar.set) self.set_sort_type(None) self.rebuild() def get_current_packages(self): packages = self.search_packages(self.current_search) if self.sort_type: packages = self.sort_packages(packages, self.sort_type) return packages def configure(self, event=None): self.rebuild() def search_packages(self, search: str = ""): if search: packages = [] for package in self.packages: try: for field in ["author", "name", "title", "description"]: if search.lower() in package[field].lower(): packages.append(package) break except: pass return packages if packages else [{ "name": "NO RESULTS", "title": "", "author": "", "updated": "" }] else: return self.packages def on_listbox_selection(self, event): selection = self.package_listbox.curselection() picked = self.package_listbox.get(selection[0]) for package in self.packages: if package["name"] == picked: self.open_details(package) def sort_packages(self, packages, sort_method): reverse = False if sort_method.endswith('-'): reverse = True sort_method = sort_method.strip('-') return sorted(packages, key=lambda k: k[sort_method], reverse=reverse) def rebuild(self): self.build_frame(self.get_current_packages()) def build_frame(self, packages): def do_build_frame(): if not packages: return self.clear() if self.listbox_list: for lb in self.listbox_list: lb.configure(state="normal") installed_packages = self.appstore_handler.get_packages( silent=True) for package in packages: if not package: continue self.package_listbox.insert('end', package["name"]) self.title_listbox.insert('end', package["title"]) self.author_listbox.insert('end', package["author"]) self.updated_listbox.insert('end', package["updated"]) if installed_packages: if package["name"] in installed_packages: if self.appstore_handler.get_package_version( package["name"]) == package["version"]: self.package_listbox.itemconfig( 'end', {"fg": "green"}) else: self.package_listbox.itemconfig( 'end', {"fg": "yellow"}) for lb in self.listbox_list: lb.configure(state="disable") self.package_listbox.configure(state='normal') bindlist = [ self, self.package_listbox, self.title_listbox, self.author_listbox, self.updated_listbox ] if platform.system() == 'Windows' or platform.system( ) == "Darwin": for b in bindlist: b.bind("<MouseWheel>", self.on_mouse_wheel) elif platform.system() == "Linux": for b in bindlist: b.bind("<Button-4>", self.on_mouse_wheel) b.bind("<Button-5>", self.on_mouse_wheel) threader.do_async(do_build_frame) def open_details(self, package): self.controller.frames["detailPage"].show(package) def select(self): self.selected = True def deselect(self): self.selected = False def is_selected(self): return self.selected def search(self, searchterm): self.current_search = searchterm self.rebuild() def clear(self): if self.listbox_list: for lb in self.listbox_list: lb.configure(state="normal") lb.delete(0, "end") lb.configure(state="disable") def on_scroll_bar(self, move_type, move_units, __=None): if move_type == "moveto": for lb in self.listbox_list: lb.yview("moveto", move_units) def on_mouse_wheel(self, event): try: if platform.system() == 'Windows': self.package_listbox.yview( "scroll", int(-1 * (event.delta / 120), "units")) elif platform.system() == "Linux": if event.num == 5: self.package_listbox.yview("scroll", 1, "units") if event.num == 4: self.package_listbox.yview("scroll", -1, "units") elif platform.system() == "Darwin": self.package_listbox.yview("scroll", event.delta, "units") for listbox in self.listbox_list: listbox.yview_moveto(self.package_listbox.yview()[0]) return "break" except: pass def set_sort_type(self, sort_type): self.sort_type = sort_type
class appstorePage(activeFrame): def __init__(self, parent, controller): self.controller = controller self.appstore_handler = controller.appstore_handler self.repo_parser = controller.repo_parser self.image_sharer = controller.image_sharer self.current_frame = None self.current_frame_name = None self.last_selection = None self.last_sort_option = None activeFrame.__init__(self, parent, controller) self.column = ThemedFrame(self, background=style.color_1) self.column.place(relx=0, rely=0, width=style.sidecolumnwidth, relheight=1) self.column_header = ThemedFrame(self.column, background=style.color_1) self.column_header.place(relx=0, rely=0, relwidth=1, height=style.headerheight) self.column_header_title = ThemedLabel(self.column_header, "HBUpdater\nGPLv3", anchor="center", label_font=style.giantboldtext, background=style.color_1) self.column_header_title.place(relx=0, rely=0, relwidth=1, relheight=1, height=-1) self.column_header_separator = ThemedLabel(self.column_header, "", background=style.lg) self.column_header_separator.place(x=style.offset, rely=1, y=-1, relwidth=1, width=-2 * style.offset) self.column_body = ThemedFrame(self.column, background=style.color_1) self.column_body.place( relx=0, relwidth=1, y=style.headerheight, relheight=1, height=-(style.headerheight + style.footerheight)) self.category_listbox = ThemedListbox(self.column_body) self.category_listbox.configure(activestyle="none") self.category_listbox.place(relwidth=1, relheight=1) self.category_listbox.bind('<<ListboxSelect>>', self.select_frame) self.column_footer = ThemedFrame(self.column, background=style.color_1) self.column_footer.place(relx=0, rely=1, relwidth=1, height=style.footerheight, y=-style.footerheight) self.column_set_sd = button(self.column_footer, callback=self.set_sd, text_string="Select SD Root", font=style.mediumboldtext, background=style.color_2).place( relwidth=1, relheight=0.5, y=style.offset, x=style.offset, width=-2 * style.offset, height=-2 * style.offset) self.column_sd_status_label = ThemedLabel( self.column_footer, "SD: Not Set", anchor="w", label_font=style.giantboldtext, background=style.color_1, foreground=style.pathdisplaytextcolor) self.column_sd_status_label.place(x=style.offset, relheight=0.5, rely=0.5, height=-style.offset, relwidth=1, width=-2 * style.offset) self.content_frame = ThemedFrame(self) self.content_frame.place(x=style.sidecolumnwidth, width=-style.sidecolumnwidth, rely=0, relheight=1, relwidth=1) self.content_frame_header = ThemedFrame(self.content_frame) self.content_frame_header.place(relx=0, rely=0, relwidth=1, height=style.searchboxheight) self.category_label = ThemedLabel(self.content_frame_header, "", anchor="w", label_font=style.giantboldtext, background=style.color_1, foreground=style.lg) self.category_label.place(relheight=1, rely=0.5, y=-0.5 * style.searchboxheight) self.content_frame_header_search_bar = searchBox( self.content_frame_header, command=self.search, entry_background=style.color_2, borderwidth=0) self.selected_sort_method = tk.StringVar() self.selected_sort_method.set(SORT_OPTIONS[0]) self.content_frame_header_sort_method_dropdown = tk.OptionMenu( self.content_frame_header, self.selected_sort_method, *SORT_OPTIONS) self.content_frame_header_sort_method_dropdown.configure( background=style.color_2) self.content_frame_header_sort_method_dropdown.configure( highlightthickness=0) self.content_frame_header_sort_method_dropdown.configure(borderwidth=0) #The various content gets stacked on top of each other here. self.content_stacking_frame = ThemedFrame(self.content_frame) self.content_stacking_frame.place( relx=0, y=(style.searchboxheight + style.offset), relwidth=1, relheight=1, height=-(style.searchboxheight + style.offset)) all_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.all) media_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.media) emus_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.emulators) games_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.games) tools_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.homebrew) python_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.nxpythonlist) cfw_frame = categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.customfirmwarelist) installed_frame = installed_categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.all) injector_frame = injector_categoryFrame(self.content_stacking_frame, self.controller, self, self.repo_parser.payloadlist) help_frame = helpFrame(self.content_stacking_frame) about_frame = aboutFrame(self.content_stacking_frame) readme_frame = readmeFrame(self.content_stacking_frame) self.category_frames = [ all_frame, media_frame, emus_frame, games_frame, tools_frame, python_frame, cfw_frame, installed_frame, injector_frame ] self.frames = [{ "frame": all_frame, "text": "All Apps" }, { "frame": games_frame, "text": "Games" }, { "frame": emus_frame, "text": "Emulators" }, { "frame": tools_frame, "text": "Homebrew" }, { "frame": media_frame, "text": "Media" }, { "frame": python_frame, "text": "Python" }, { "frame": cfw_frame, "text": "Custom Firmware" }, { "frame": installed_frame, "text": "Installed" }, { "frame": injector_frame, "text": "RCM Injector" }, { "frame": help_frame, "text": "HELP" }, { "frame": about_frame, "text": "ABOUT" }, { "frame": readme_frame, "text": "README", }] #Add pages as frames to dict, with keyword being the name of the frame self.content_frames = {} for E in self.frames: page_name = E["text"] frame = E["frame"] self.content_frames[page_name] = frame frame.place(relx=0, rely=0, relwidth=1, relheight=1) self.category_listbox.insert("end", " {}".format(page_name)) self.category_listbox.select_set( 0) #sets focus on the first item in listbox self.category_listbox.event_generate("<<ListboxSelect>>") self.bind("<<ShowFrame>>", self.reset_selection) self.show_frame("All Apps") if self.controller.updater.status: print(self.controller.updater.status) self.yesnoPage = yesnoPage(self) self.yesnoPage.getanswer( "An update is available, would you like to download it?\nPatch notes:\n{}" .format(self.controller.updater.status), self.controller.updater.update) self.loaded() self.add_on_refresh_callback(self.update_sd_path) self.sort_check_loop() def reset_selection(self, event=None): self.category_listbox.select_set( self.category_listbox.get(0, "end").index(" {}".format( self.current_frame_name))) def show_frame(self, page_name): #Show a frame for the given page name frame = self.content_frames[page_name] frame.event_generate("<<ShowFrame>>") frame.tkraise() self.category_label.set(page_name) self.controller.after(100, self.update_search_bar_position) for frm in self.category_frames: frm.deselect() if frame in self.category_frames: frame.select() self.current_frame = frame self.current_frame_name = page_name def update_search_bar_position(self): if not self.current_frame in self.category_frames: self.content_frame_header_search_bar.place_forget() self.content_frame_header_sort_method_dropdown.place_forget() else: category_label_offset = self.category_label.winfo_width() #If the category label has been populated, otherwise the offset is usually just a few pixels (prevents an ugly draw on launch) if category_label_offset > style.offset: self.content_frame_header_sort_method_dropdown.place( relx=1, x=-(style.offset + style.sortdropdownwidth), width=style.sortdropdownwidth, rely=0.5, y=-0.5 * style.searchboxheight + style.offset, relheight=1, height=-2 * style.offset) self.content_frame_header_search_bar.place( x=category_label_offset + style.offset, rely=0.5, y=-0.5 * style.searchboxheight + style.offset, relheight=1, relwidth=1, width=-(category_label_offset + 4 * style.offset + style.sortdropdownwidth), height=-2 * style.offset) else: self.content_frame_header_search_bar.place_forget() self.controller.after(100, self.update_search_bar_position) def select_frame(self, event): try: widget = event.widget selection = widget.curselection() picked = widget.get(selection[0]) if not picked == self.last_selection: frame = None for f in self.frames: t = f["text"] if t.strip() == picked.strip(): self.show_frame(t) break self.last_selection = picked except Exception as e: # print(e) pass def search(self, searchterm): self.current_frame.search(searchterm) def reload_category_frames(self): print("Reloading frames") for frame in self.category_frames: frame.configure(None) def set_sd(self): chosensdpath = tkinter.filedialog.askdirectory( initialdir="/", title='Please select your SD card') self.appstore_handler.set_path(chosensdpath) self.reload_category_frames() self.update_sd_path() def update_sd_path(self): chosensdpath = self.appstore_handler.check_path() if chosensdpath: #Get the basename basepath = os.path.basename(os.path.normpath(chosensdpath)) #If we didn't find it, assume it's a root dir and just return the whole path if not basepath: basepath = chosensdpath else: basepath = "Not Set" self.column_sd_status_label.set("SD: {}".format(basepath)) def update_sort(self): for frame in self.category_frames: frame.set_sort_type(SORT_MAP[self.selected_sort_method.get()]) self.current_frame.rebuild() #loop to check if the sorting methog has been applied yet def sort_check_loop(self): if not self.last_sort_option == self.selected_sort_method.get(): self.last_sort_option = self.selected_sort_method.get() self.update_sort() #schedule self self.schedule_callback(self.sort_check_loop, 100)