From 1be041acc7e5bab7b8b099fa83f38cb2a2bbeae3 Mon Sep 17 00:00:00 2001 From: Jesper Date: Wed, 12 Jun 2024 20:30:44 +0200 Subject: [PATCH 1/4] Fix search and add fuzzy search option --- .../mintMenu/plugins/applications.py | 135 ++++++++++++++---- usr/lib/linuxmint/mintMenu/preferences.py | 1 + .../com.linuxmint.mintmenu.gschema.xml | 6 + 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py index e215ec6..3376d21 100755 --- a/usr/lib/linuxmint/mintMenu/plugins/applications.py +++ b/usr/lib/linuxmint/mintMenu/plugins/applications.py @@ -8,6 +8,8 @@ import subprocess import threading import urllib.request, urllib.parse, urllib.error +from fuzzywuzzy import process +import unidecode import gi gi.require_version("Gtk", "3.0") @@ -274,9 +276,11 @@ def __init__(self, mintMenuWin, toggleButton, de): self.settings.connect("changed::fav-cols", self.changeFavCols) self.settings.connect("changed::remember-filter", self.changeRememberFilter) self.settings.connect("changed::enable-internet-search", self.changeEnableInternetSearch) + self.settings.connect("changed::enable-fuzzy-search", self.changeEnableFuzzySearch) self.settings.connect("changed::category-hover-delay", self.GetGSettingsEntries) self.settings.connect("changed::do-not-filter", self.GetGSettingsEntries) self.settings.connect("changed::enable-internet-search", self.GetGSettingsEntries) + self.settings.connect("changed::enable-fuzzy-search", self.GetGSettingsEntries) self.settings.connect("changed::search-command", self.GetGSettingsEntries) self.settings.connect("changed::default-tab", self.GetGSettingsEntries) self.settings.connect("changed::favorite-apps-list", self.favoriteAppsChanged) @@ -302,6 +306,7 @@ def __init__(self, mintMenuWin, toggleButton, de): self.categoryList = [] self.applicationList = [] + self.sortedApplicationList = [] #dirty ugly hack, to get favorites drag origin position self.drag_origin = None @@ -441,6 +446,9 @@ def changeRememberFilter(self, settings, key): def changeEnableInternetSearch(self, settings, key): self.enableInternetSearch = settings.get_boolean(key) + def changeEnableFuzzySearch(self, settings, key): + self.enableFuzzySearch = settings.get_boolean(key) + def changeShowApplicationComments(self, settings, key): self.showapplicationcomments = settings.get_boolean(key) for child in self.applicationsBox: @@ -516,6 +524,7 @@ def GetGSettingsEntries(self, settings=None, key=None): self.useAPT = self.settings.get_boolean("use-apt") self.rememberFilter = self.settings.get_boolean("remember-filter") self.enableInternetSearch = self.settings.get_boolean("enable-internet-search") + self.enableFuzzySearch = self.settings.get_boolean("enable-fuzzy-search") self.lastActiveTab = self.settings.get_int("last-active-tab") self.defaultTab = self.settings.get_int("default-tab") @@ -745,6 +754,86 @@ def add_apt_filter_results_sync(self, cache, keyword): except Exception as e: print(e) + def strip_case_and_accents(self, string): + if isinstance(string, str): + try: + value = unidecode.unidecode(string.lower()) + except: + pass + return value + + def fuzzy_application_search(self, search_text): + shownList = [] + showns = False # Are any app shown? + keywords = self.strip_case_and_accents(search_text).split() + labels = [app["button"].appName for app in self.applicationList] + results = process.extract(search_text, labels, limit=len(labels)) + + first_button = True + + for match in results: + if match[1] > 60: # Adjust the threshold as needed + for app in self.applicationList: + if app["button"].appName == match[0]: + self.applicationsBox.pack_start(app["button"], False, False, 0) + if first_button is True: + app["button"].grab_focus() + first_button = False + shownList.append(app["button"]) + showns = True + + # Non-fuzzy results for appGenericName, appComment and appExec + # Again I should really make it a function or something + for app in self.applicationList: + for item in shownList: + if app["button"].desktopFile == item.desktopFile: + continue + for keyword in keywords: + if keyword != "" and (self.strip_case_and_accents(app["button"].appGenericName).find(keyword) != -1 or self.strip_case_and_accents(app["button"].appComment).find(keyword) != -1 or self.strip_case_and_accents(app["button"].appExec).find(keyword) != -1): + self.applicationsBox.pack_start(app["button"], False, False, 0) + if first_button is True: + app["button"].grab_focus() + first_button = False + shownList.append(app["button"]) + showns = True + return showns + + + def exact_application_search(self, search_text): + shownList = [] + showns = False # Are any app shown? + first_button = True + keywords = self.strip_case_and_accents(search_text).split() + # I should probably make a function but whatever + # Add applications that match the appName + for app in self.applicationList: + for item in shownList: + if app["button"].desktopFile == item.desktopFile: + continue + for keyword in keywords: + if keyword != "" and (self.strip_case_and_accents(app["button"].appName).find(keyword) != -1): + self.applicationsBox.pack_start(app["button"], False, False, 0) + if first_button is True: + app["button"].grab_focus() + first_button = False + shownList.append(app["button"]) + showns = True + + # Add applications that match appGenericName, appComment or appExec + for app in self.applicationList: + for item in shownList: + if app["button"].desktopFile == item.desktopFile: + continue + for keyword in keywords: + if keyword != "" and (self.strip_case_and_accents(app["button"].appGenericName).find(keyword) != -1 or self.strip_case_and_accents(app["button"].appComment).find(keyword) != -1 or self.strip_case_and_accents(app["button"].appExec).find(keyword) != -1): + self.applicationsBox.pack_start(app["button"], False, False, 0) + if first_button is True: + app["button"].grab_focus() + first_button = False + shownList.append(app["button"]) + showns = True + return showns + def Filter(self, widget, category = None): self.filterTimer = None @@ -761,23 +850,20 @@ def Filter(self, widget, category = None): self.changeTab(1, clear = False) text = widget.get_text() showns = False # Are any app shown? - shownList = [] - for i in self.applicationsBox.get_children(): - shown = i.filterText(text) - if shown: - dupe = False - for item in shownList: - if i.desktopFile == item.desktopFile: - dupe = True - if dupe: - i.hide() - else: - shownList.append(i) - #if this is the first matching item - #focus it - if(not showns): - i.grab_focus() - showns = True + + for item in self.applicationList: + self.applicationsBox.remove(item["button"]) + + if text == "": # Reset application list + for item in self.applicationList: + self.applicationsBox.remove(item["button"]) + for item in self.sortedApplicationList: + self.applicationsBox.pack_start(item[1], False, False, 0) + if self.enableFuzzySearch: + showns = self.fuzzy_application_search(text) + else: + showns = self.exact_application_search(text) + if not showns: if len(text) >= 3: self.add_search_suggestions(text) @@ -809,7 +895,6 @@ def Filter(self, widget, category = None): i.released() i.set_relief(Gtk.ReliefStyle.NONE) widget.set_relief(Gtk.ReliefStyle.HALF) - self.applicationsScrolledWindow.get_vadjustment().set_value(0) def FilterAndClear(self, widget, category = None): @@ -1592,10 +1677,10 @@ def updateBoxes(self, menu_has_changed): self.applicationList[key]["button"].destroy() del self.applicationList[key] if addedApplications: - sortedApplicationList = [] + self.sortedApplicationList = [] for item in self.applicationList: self.applicationsBox.remove(item["button"]) - sortedApplicationList.append((item["button"].appName, item["button"])) + self.sortedApplicationList.append((item["button"].appName, item["button"])) for item in addedApplications: item["button"] = MenuApplicationLauncher(item["entry"].get_desktop_file_path(), self.iconSize, item["category"], self.showapplicationcomments, @@ -1603,21 +1688,21 @@ def updateBoxes(self, menu_has_changed): if item["button"].appExec: self.mintMenuWin.setTooltip(item["button"], item["button"].getTooltip()) item["button"].connect("button-press-event", self.menuPopup) - item["button"].connect("focus-in-event", self.scrollItemIntoView) + # item["button"].connect("focus-in-event", self.scrollItemIntoView) item["button"].connect("clicked", RecentHelper.applicationButtonClicked) if self.activeFilter[0] == 0: item["button"].filterText(self.activeFilter[1]) else: item["button"].filterCategory(self.activeFilter[1]) item["button"].desktop_file_path = item["entry"].get_desktop_file_path() - sortedApplicationList.append((item["button"].appName.upper(), item["button"])) + self.sortedApplicationList.append((item["button"].appName.upper(), item["button"])) self.applicationList.append(item) else: item["button"].destroy() - sortedApplicationList.sort() + self.sortedApplicationList.sort() launcherNames = [] # Keep track of launcher names so we don't add them twice in the list.. - for item in sortedApplicationList: + for item in self.sortedApplicationList: launcherName = item[0] button = item[1] self.applicationsBox.add(button) @@ -1694,4 +1779,4 @@ def find_applications_recursively(app_list, directory, catName): # print "=======>>> " + item.get_name() + " = top level" # newApplicationsList.append({"entry": item, "category": ""}) - return newApplicationsList + return newApplicationsList \ No newline at end of file diff --git a/usr/lib/linuxmint/mintMenu/preferences.py b/usr/lib/linuxmint/mintMenu/preferences.py index fd92981..016a36f 100755 --- a/usr/lib/linuxmint/mintMenu/preferences.py +++ b/usr/lib/linuxmint/mintMenu/preferences.py @@ -134,6 +134,7 @@ def __init__(self): section.add_row(GSettingsSwitch(_("Search for packages to install"), "com.linuxmint.mintmenu.plugins.applications", "use-apt")) section.add_row(GSettingsSwitch(_("Remember the last category or search"), "com.linuxmint.mintmenu.plugins.applications", "remember-filter")) section.add_row(GSettingsSwitch(_("Enable Internet search"), "com.linuxmint.mintmenu.plugins.applications", "enable-internet-search")) + section.add_row(GSettingsSwitch(_("Enable Fuzzy search"), "com.linuxmint.mintmenu.plugins.applications", "enable-fuzzy-search")) section.add_row(GSettingsEntry(_("Search command"), "com.linuxmint.mintmenu.plugins.applications", "search-command")) page = SettingsPage() diff --git a/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml b/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml index d75c06c..aa3c7de 100644 --- a/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml +++ b/usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml @@ -286,6 +286,12 @@ + + false + + + + false From 7ade68f10f07b5235c972e15903315694b2076d6 Mon Sep 17 00:00:00 2001 From: Jesper Date: Wed, 12 Jun 2024 20:39:12 +0200 Subject: [PATCH 2/4] Newline consistency --- usr/lib/linuxmint/mintMenu/plugins/applications.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py index 3376d21..0821d75 100755 --- a/usr/lib/linuxmint/mintMenu/plugins/applications.py +++ b/usr/lib/linuxmint/mintMenu/plugins/applications.py @@ -798,7 +798,6 @@ def fuzzy_application_search(self, search_text): showns = True return showns - def exact_application_search(self, search_text): shownList = [] showns = False # Are any app shown? @@ -895,6 +894,7 @@ def Filter(self, widget, category = None): i.released() i.set_relief(Gtk.ReliefStyle.NONE) widget.set_relief(Gtk.ReliefStyle.HALF) + self.applicationsScrolledWindow.get_vadjustment().set_value(0) def FilterAndClear(self, widget, category = None): @@ -1779,4 +1779,5 @@ def find_applications_recursively(app_list, directory, catName): # print "=======>>> " + item.get_name() + " = top level" # newApplicationsList.append({"entry": item, "category": ""}) - return newApplicationsList \ No newline at end of file + return newApplicationsList + \ No newline at end of file From 8778209e30a4ac39d7a8b519bd188cc65420c3b5 Mon Sep 17 00:00:00 2001 From: Jesper Date: Wed, 12 Jun 2024 20:43:32 +0200 Subject: [PATCH 3/4] Remove 4 spaces at the end of the file --- usr/lib/linuxmint/mintMenu/plugins/applications.py | 1 - 1 file changed, 1 deletion(-) diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py index 0821d75..20d7943 100755 --- a/usr/lib/linuxmint/mintMenu/plugins/applications.py +++ b/usr/lib/linuxmint/mintMenu/plugins/applications.py @@ -1780,4 +1780,3 @@ def find_applications_recursively(app_list, directory, catName): # newApplicationsList.append({"entry": item, "category": ""}) return newApplicationsList - \ No newline at end of file From 19528a9c8824464f08736a724b66d1467aad3dd9 Mon Sep 17 00:00:00 2001 From: Jesper Date: Thu, 13 Jun 2024 08:14:47 +0200 Subject: [PATCH 4/4] Bring back scrollItemIntoView --- usr/lib/linuxmint/mintMenu/plugins/applications.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py index 20d7943..18c3ba0 100755 --- a/usr/lib/linuxmint/mintMenu/plugins/applications.py +++ b/usr/lib/linuxmint/mintMenu/plugins/applications.py @@ -588,6 +588,7 @@ def blockOnRightPress(self, widget, event): return False def focusSearchEntry(self, clear = True): + self.applicationsBox.get_children()[0].grab_focus() # grab_focus() does select all text, # restoring the original selection is somehow broken, so just select the end # of the existing text, that's the most likely candidate anyhow @@ -1688,7 +1689,7 @@ def updateBoxes(self, menu_has_changed): if item["button"].appExec: self.mintMenuWin.setTooltip(item["button"], item["button"].getTooltip()) item["button"].connect("button-press-event", self.menuPopup) - # item["button"].connect("focus-in-event", self.scrollItemIntoView) + item["button"].connect("focus-in-event", self.scrollItemIntoView) item["button"].connect("clicked", RecentHelper.applicationButtonClicked) if self.activeFilter[0] == 0: item["button"].filterText(self.activeFilter[1])