From fce41f73bab702acd9cb1235d55b53a757aa6257 Mon Sep 17 00:00:00 2001 From: D3V1L0N <167227445+D3V1L0N@users.noreply.github.com> Date: Tue, 23 Apr 2024 14:46:05 +0530 Subject: [PATCH] contribution on GUI.py --- blackbox/ui/GUI.py | 722 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 721 insertions(+), 1 deletion(-) diff --git a/blackbox/ui/GUI.py b/blackbox/ui/GUI.py index 8e53ab8..c8e601d 100644 --- a/blackbox/ui/GUI.py +++ b/blackbox/ui/GUI.py @@ -1 +1,721 @@ -#!/bin/python \ No newline at end of file +#!/bin/python .venv +import Functions as fn +from ui.AppFrameGUI import AppFrameGUI +from multiprocessing import cpu_count +from queue import Queue +from threading import Thread + +base_dir = fn.os.path.abspath(fn.os.path.join(fn.os.path.dirname(__file__), "..")) +# base_dir = fn.os.path.dirname(fn.os.path.realpath(__file__)) + + +class GUI_Worker(Thread): + def __init__(self, queue): + Thread.__init__(self) + self.queue = queue + + def run(self): + while True: + # pull what we need from the queue so we can process properly. + items = self.queue.get() + + try: + # make sure we have the required number of items on the queue + if items is not None: + # self, Gtk, vboxStack1, category, package_file = items + + self, Gtk, vbox_stack, category, packages = items + + AppFrameGUI.build_ui_frame( + self, + Gtk, + vbox_stack, + category, + packages, + ) + + except Exception as e: + fn.logger.error("Exception in GUI_Worker(): %s" % e) + finally: + if items is None: + fn.logger.debug("Stopping GUI Worker thread") + self.queue.task_done() + return False + self.queue.task_done() + + +class GUI: + def setup_gui_search( + self, + Gtk, + Gdk, + GdkPixbuf, + base_dir, + os, + Pango, + search_results, + search_term, + settings, + ): + try: + # remove previous vbox + if self.search_activated == False: + self.remove(self.vbox) + else: + self.remove(self.vbox_search) + + # lets quickly create the latest installed list. + fn.get_current_installed() + + # ======================================================= + # HeaderBar + # ======================================================= + + setup_headerbar(self, Gtk, settings) + + # ======================================================= + # App Notifications + # ======================================================= + + hbox0 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + + self.notification_revealer = Gtk.Revealer() + self.notification_revealer.set_reveal_child(False) + + self.notification_label = Gtk.Label() + + pb_panel = GdkPixbuf.Pixbuf().new_from_file(base_dir + "/images/panel.png") + panel = Gtk.Image().new_from_pixbuf(pb_panel) + + overlay_frame = Gtk.Overlay() + overlay_frame.add(panel) + overlay_frame.add_overlay(self.notification_label) + + self.notification_revealer.add(overlay_frame) + + hbox0.pack_start(self.notification_revealer, True, False, 0) + + # ========================================================== + # CONTAINER + # ========================================================== + + self.vbox_search = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) + + self.vbox_search.pack_start(hbox, True, True, 0) + self.add(self.vbox_search) + + # ========================================================== + # PREP WORK + # ========================================================== + + # This section sets up the tabs, and the array for dealing with the tab content + + # ========================================================== + # GENERATE STACK + # ========================================================== + stack = Gtk.Stack() + # stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) + stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) + stack.set_transition_duration(350) + + vbox_stack = [] + stack_item = 0 + + # Max Threads + """ + Fatal Python error: Segmentation fault + This error happens randomly, due to the for loop iteration on the cpu_count + old code: for x in range(cpu_count()): + """ + + # spawn only 1 GUI_Worker threads, as any number greater causes a Segmentation fault + + search_worker = GUI_Worker(self.queue) + search_worker.name = "thread_GUI_search_worker" + # Set the worker to be True to allow processing, and avoid Blocking + # search_worker.daemon = True + search_worker.start() + + # This code section might look a little weird. It is because it was + # derived from another function before this version was required. + + for category in search_results: + # NOTE: IF the yaml file name standard changes, be sure to update this, or weirdness will follow. + + # subcategory = search_results[category][0].subcategory + vbox_stack.append( + Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + ) + stack.add_titled( + vbox_stack[stack_item], + str("stack" + str(len(vbox_stack))), + category, + ) + + # subcategory_desc = search_results[category][0].subcategory_description + search_res_lst = search_results[category] + + # Multithreading! + + self.queue.put( + ( + self, + Gtk, + vbox_stack[stack_item], + category, + search_res_lst, + ) + ) + + stack_item += 1 + + # send a signal that no further items are to be put on the queue + self.queue.put(None) + # safety to ensure that we finish threading before we continue on. + self.queue.join() + fn.logger.debug("GUI Worker thread completed") + + stack_switcher = Gtk.StackSidebar() + stack_switcher.set_name("sidebar") + stack_switcher.set_stack(stack) + + # ===================================================== + # LOGO + # ===================================================== + + ivbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + pixbuf = GdkPixbuf.Pixbuf().new_from_file_at_size( + os.path.join(base_dir, "images/sofirem.png"), 45, 45 + ) + image = Gtk.Image().new_from_pixbuf(pixbuf) + + # remove the focus on startup from search entry + ivbox.set_property("can-focus", True) + Gtk.Window.grab_focus(ivbox) + + # ===================================================== + # RECACHE BUTTON + # ===================================================== + + btn_recache = Gtk.Button(label="Recache Applications") + btn_recache.connect("clicked", self.recache_clicked) + # btnReCache.set_property("has-tooltip", True) + # btnReCache.connect("query-tooltip", self.tooltip_callback, + # "Refresh the application cache") + + # ===================================================== + # REPOS + # ===================================================== + + # if not ( + # fn.check_package_installed("arcolinux-keyring") + # or fn.check_package_installed("arcolinux-mirrorlist-git") + # ): + # self.btnRepos = Gtk.Button(label="Add ArcoLinux Repo") + # self.btnRepos._value = 1 + # else: + # self.btnRepos = Gtk.Button(label="Remove ArcoLinux Repo") + # self.btnRepos._value = 2 + # + # self.btnRepos.set_size_request(100, 30) + # self.btnRepos.connect("clicked", self.on_repos_clicked) + + # ===================================================== + # QUIT BUTTON + # ===================================================== + + btn_quit_app = Gtk.Button(label="Quit") + btn_quit_app.set_size_request(100, 30) + btn_quit_app.connect("clicked", self.on_close, "delete-event") + btn_context = btn_quit_app.get_style_context() + btn_context.add_class("destructive-action") + + # ===================================================== + # SEARCH BOX + # ===================================================== + + self.searchentry = Gtk.SearchEntry() + self.searchentry.set_text(search_term) + self.searchentry.connect("activate", self.on_search_activated) + self.searchentry.connect("icon-release", self.on_search_cleared) + + iv_searchbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + # ===================================================== + # PACKS + # ===================================================== + + # hbox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) + # hbox2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) + # hbox3 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=2) + + # hbox3.pack_start(btnReCache, False, False, 0) + + iv_searchbox.pack_start(self.searchentry, False, False, 0) + + ivbox.pack_start(image, False, False, 0) + ivbox.pack_start(iv_searchbox, False, False, 0) + ivbox.pack_start(stack_switcher, True, True, 0) + + ivbox.pack_start(btn_quit_app, False, False, 0) + + vbox1.pack_start(hbox0, False, False, 0) + vbox1.pack_start(stack, True, True, 0) + + hbox.pack_start(ivbox, False, True, 0) + hbox.pack_start(vbox1, True, True, 0) + + stack.set_hhomogeneous(False) + stack.set_vhomogeneous(False) + + self.show_all() + + except Exception as err: + fn.logger.error("Exception in GUISearch(): %s" % err) + + def setup_gui(self, Gtk, Gdk, GdkPixbuf, base_dir, os, Pango, settings): # noqa + try: + # reset back to main box + if self.search_activated: + # remove the search vbox + self.remove(self.vbox_search) + self.show_all() + + # lets quickly create the latest installed list. + fn.get_current_installed() + + # ======================================================= + # HeaderBar + # ======================================================= + + setup_headerbar(self, Gtk, settings) + + # ======================================================= + # App Notifications + # ======================================================= + + hbox0 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + + self.notification_revealer = Gtk.Revealer() + self.notification_revealer.set_reveal_child(False) + + self.notification_label = Gtk.Label() + + pb_panel = GdkPixbuf.Pixbuf().new_from_file(base_dir + "/images/panel.png") + panel = Gtk.Image().new_from_pixbuf(pb_panel) + + overlay_frame = Gtk.Overlay() + overlay_frame.add(panel) + overlay_frame.add_overlay(self.notification_label) + + self.notification_revealer.add(overlay_frame) + + hbox0.pack_start(self.notification_revealer, True, False, 0) + + # ========================================================== + # CONTAINER + # ========================================================== + + self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) + + self.vbox.pack_start(hbox, True, True, 0) + self.add(self.vbox) + + # ========================================================== + # PREP WORK + # ========================================================== + + # This section sets up the tabs, and the array for dealing with the tab content + """ + yaml_files_unsorted = [] + path = base_dir + "/yaml/" + for file in os.listdir(path): + if file.endswith(".yaml"): + yaml_files_unsorted.append(file) + else: + print( + "Unsupported configuration file type. Please contact Arcolinux Support." + ) + # Need to sort the list (Or do we? I choose to) + yaml_files = sorted(yaml_files_unsorted) + """ + + # Check github for updated files + # fn.check_github(yaml_files) + # ========================================================== + # GENERATE STACK + # ========================================================== + stack = Gtk.Stack() + # stack.set_transition_type(Gtk.StackTransitionType.SLIDE_UP_DOWN) + stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE) + stack.set_transition_duration(350) + + vbox_stack = [] + stack_item = 0 + + # Max Threads + """ + Fatal Python error: Segmentation fault + This error happens randomly, due to the for loop iteration on the cpu_count + old code: for x in range(cpu_count()): + """ + + # spawn only 1 GUI_Worker threads, as any number greater causes a Segmentation fault + + worker = GUI_Worker(self.queue) + worker.name = "thread_GUI_Worker" + # Set the worker to be True to allow processing, and avoid Blocking + # worker.daemon = True + worker.start() + + for category in self.packages: + # NOTE: IF the yaml file name standard changes, be sure to update this, or weirdness will follow. + + # this is the side stack listing all categories + vbox_stack.append( + Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + ) + stack.add_titled( + vbox_stack[stack_item], + str("stack" + str(len(vbox_stack))), + category, + ) + + packages_lst = self.packages[category] + + # Multithreading! + self.queue.put( + ( + self, + Gtk, + vbox_stack[stack_item], + category, + packages_lst, + ) + ) + stack_item += 1 + + # send a signal that no further items are to be put on the queue + self.queue.put(None) + # safety to ensure that we finish threading before we continue on. + + self.queue.join() + fn.logger.debug("GUI Worker thread completed") + + stack_switcher = Gtk.StackSidebar() + stack_switcher.set_name("sidebar") + stack_switcher.set_stack(stack) + + # ===================================================== + # LOGO + # ===================================================== + + ivbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + pixbuf = GdkPixbuf.Pixbuf().new_from_file_at_size( + os.path.join(base_dir, "images/sofirem.png"), 45, 45 + ) + image = Gtk.Image().new_from_pixbuf(pixbuf) + + # remove the focus on startup from search entry + ivbox.set_property("can-focus", True) + Gtk.Window.grab_focus(ivbox) + + # ===================================================== + # RECACHE BUTTON + # ===================================================== + + # btnReCache = Gtk.Button(label="Recache Applications") + # btnReCache.connect("clicked", self.recache_clicked) + # btnReCache.set_property("has-tooltip", True) + # btnReCache.connect("query-tooltip", self.tooltip_callback, + # "Refresh the application cache") + + # ===================================================== + # REPOS + # ===================================================== + + # ===================================================== + # QUIT BUTTON + # ===================================================== + btn_quit_app = Gtk.Button(label="Quit") + btn_quit_app.set_size_request(100, 30) + btn_quit_app.connect("clicked", self.on_close, "delete-event") + btn_context = btn_quit_app.get_style_context() + btn_context.add_class("destructive-action") + # ===================================================== + # SEARCH BOX + # ===================================================== + self.searchentry = Gtk.SearchEntry() + self.searchentry.set_placeholder_text("Search...") + self.searchentry.connect("activate", self.on_search_activated) + self.searchentry.connect("icon-release", self.on_search_cleared) + + ivsearchbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + ivsearchbox.pack_start(self.searchentry, False, False, 0) + + ivbox.pack_start(image, False, False, 0) + ivbox.pack_start(ivsearchbox, False, False, 0) + ivbox.pack_start(stack_switcher, True, True, 0) + ivbox.pack_start(btn_quit_app, False, False, 0) + + vbox1.pack_start(hbox0, False, False, 0) + vbox1.pack_start(stack, True, True, 0) + + hbox.pack_start(ivbox, False, True, 0) + hbox.pack_start(vbox1, True, True, 0) + + stack.set_hhomogeneous(False) + stack.set_vhomogeneous(False) + + if self.search_activated: + self.show_all() + + except Exception as e: + fn.logger.error("Exception in GUI(): %s" % e) + + +# setup headerbar including popover settings +def setup_headerbar(self, Gtk, settings): + try: + header_bar_title = "Sofirem" + headerbar = Gtk.HeaderBar() + headerbar.set_title(header_bar_title) + headerbar.set_show_close_button(True) + + self.set_titlebar(headerbar) + + toolbutton = Gtk.ToolButton() + # icon-name open-menu-symbolic / open-menu-symbolic.symbolic + toolbutton.set_icon_name("open-menu-symbolic") + + toolbutton.connect("clicked", self.on_settings_clicked) + + headerbar.pack_end(toolbutton) + + self.popover = Gtk.Popover() + self.popover.set_relative_to(toolbutton) + + vbox = Gtk.Box(spacing=0, orientation=Gtk.Orientation.VERTICAL) + vbox.set_border_width(15) + + # switches + + # switch to display package versions + self.switch_package_version = Gtk.Switch() + + if settings != None: + if settings["Display Package Versions"]: + self.display_versions = settings["Display Package Versions"] + + if self.display_versions == True: + self.switch_package_version.set_active(True) + else: + self.switch_package_version.set_active(False) + + self.switch_package_version.connect("notify::active", self.version_toggle) + + # switch to import arcolinux keyring + self.switch_arco_keyring = Gtk.Switch() + + if ( + fn.check_package_installed("arcolinux-keyring") is False + or fn.verify_arco_pacman_conf() is False + ): + self.switch_arco_keyring.set_state(False) + + else: + self.switch_arco_keyring.set_state(True) + + self.switch_arco_keyring.connect("state-set", self.arco_keyring_toggle) + + # switch to import arcolinix mirrorlist + self.switch_arco_mirrorlist = Gtk.Switch() + + if ( + fn.check_package_installed("arcolinux-mirrorlist-git") is False + or fn.verify_arco_pacman_conf() is False + ): + self.switch_arco_mirrorlist.set_state(False) + + else: + self.switch_arco_mirrorlist.set_state(True) + + self.switch_arco_mirrorlist.connect("state-set", self.arco_mirrorlist_toggle) + + # switch to display package progress window + self.switch_package_progress = Gtk.Switch() + + if settings != None: + if settings["Display Package Progress"]: + self.display_package_progress = settings["Display Package Progress"] + + if self.display_package_progress == True: + self.switch_package_progress.set_active(True) + else: + self.switch_package_progress.set_active(False) + self.switch_package_progress.connect( + "notify::active", self.package_progress_toggle + ) + + # modalbuttons + + # button to open the pacman log monitoring dialog + self.modelbtn_pacmanlog = Gtk.ModelButton() + self.modelbtn_pacmanlog.connect("clicked", self.on_pacman_log_clicked) + self.modelbtn_pacmanlog.set_name("modelbtn_popover") + self.modelbtn_pacmanlog.props.centered = False + self.modelbtn_pacmanlog.props.text = "Open Pacman Log File" + + # button to display installed packages window + modelbtn_packages_export = Gtk.ModelButton() + modelbtn_packages_export.connect("clicked", self.on_packages_export_clicked) + modelbtn_packages_export.set_name("modelbtn_popover") + modelbtn_packages_export.props.centered = False + modelbtn_packages_export.props.text = "Show Installed Packages" + + # button to display import packages window + modelbtn_packages_import = Gtk.ModelButton() + modelbtn_packages_import.connect("clicked", self.on_packages_import_clicked) + modelbtn_packages_import.set_name("modelbtn_popover") + modelbtn_packages_import.props.centered = False + modelbtn_packages_import.props.text = "Import Packages" + + # button to show about dialog + modelbtn_about_app = Gtk.ModelButton() + modelbtn_about_app.connect("clicked", self.on_about_app_clicked) + modelbtn_about_app.set_name("modelbtn_popover") + modelbtn_about_app.props.centered = False + modelbtn_about_app.props.text = "About Sofirem" + + # button to show iso package lists window + modelbtn_iso_packages_list = Gtk.ModelButton() + modelbtn_iso_packages_list.connect( + "clicked", self.on_arcolinux_iso_packages_clicked + ) + modelbtn_iso_packages_list.set_name("modelbtn_popover") + modelbtn_iso_packages_list.props.centered = False + modelbtn_iso_packages_list.props.text = "Explore ArcoLinux ISO Packages" + + # button to show package search window + modelbtn_package_search = Gtk.ModelButton() + modelbtn_package_search.connect("clicked", self.on_package_search_clicked) + modelbtn_package_search.set_name("modelbtn_popover") + modelbtn_package_search.props.centered = False + modelbtn_package_search.props.text = "Open Package Search" + + # grid for the switch options + grid_switches = Gtk.Grid() + grid_switches.set_row_homogeneous(True) + + lbl_package_version = Gtk.Label(xalign=0) + lbl_package_version.set_text("Display Package Versions") + + lbl_package_version_padding = Gtk.Label(xalign=0) + lbl_package_version_padding.set_text(" ") + + lbl_package_progress = Gtk.Label(xalign=0) + lbl_package_progress.set_text("Display Package Progress") + + lbl_package_progress_padding = Gtk.Label(xalign=0) + lbl_package_progress_padding.set_text(" ") + + lbl_arco_keyring = Gtk.Label(xalign=0) + lbl_arco_keyring.set_text("Import ArcoLinux Keyring") + + lbl_arco_keyring_padding = Gtk.Label(xalign=0) + lbl_arco_keyring_padding.set_text(" ") + + lbl_arco_mirrorlist = Gtk.Label(xalign=0) + lbl_arco_mirrorlist.set_text("Import ArcoLinux Mirrorlist") + + lbl_arco_mirrorlist_padding = Gtk.Label(xalign=0) + lbl_arco_mirrorlist_padding.set_text(" ") + + grid_switches.attach(lbl_package_version, 0, 1, 1, 1) + grid_switches.attach_next_to( + lbl_package_version_padding, + lbl_package_version, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach_next_to( + self.switch_package_version, + lbl_package_version_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach(lbl_package_progress, 0, 2, 1, 1) + grid_switches.attach_next_to( + lbl_package_progress_padding, + lbl_package_progress, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach_next_to( + self.switch_package_progress, + lbl_package_progress_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach(lbl_arco_keyring, 0, 3, 1, 1) + grid_switches.attach_next_to( + lbl_arco_keyring_padding, + lbl_arco_keyring, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach_next_to( + self.switch_arco_keyring, + lbl_arco_keyring_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach(lbl_arco_mirrorlist, 0, 4, 1, 1) + grid_switches.attach_next_to( + lbl_arco_mirrorlist_padding, + lbl_arco_mirrorlist, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + grid_switches.attach_next_to( + self.switch_arco_mirrorlist, + lbl_arco_mirrorlist_padding, + Gtk.PositionType.RIGHT, + 1, + 1, + ) + + vbox_buttons = Gtk.Box(spacing=1, orientation=Gtk.Orientation.VERTICAL) + vbox_buttons.pack_start(self.modelbtn_pacmanlog, False, True, 0) + vbox_buttons.pack_start(modelbtn_packages_export, False, True, 0) + vbox_buttons.pack_start(modelbtn_packages_import, False, True, 0) + vbox_buttons.pack_start(modelbtn_iso_packages_list, False, True, 0) + vbox_buttons.pack_start(modelbtn_package_search, False, True, 0) + vbox_buttons.pack_start(modelbtn_about_app, False, True, 0) + + vbox.pack_start(grid_switches, False, False, 0) + vbox.pack_start(vbox_buttons, False, False, 0) + + self.popover.add(vbox) + self.popover.set_position(Gtk.PositionType.BOTTOM) + except Exception as e: + fn.logger.error("Exception in setup_headerbar(): %s" % e)