diff --git a/usr/lib/linuxmint/mintMenu/plugins/applications.py b/usr/lib/linuxmint/mintMenu/plugins/applications.py
index e215ec6..18c3ba0 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")
@@ -579,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
@@ -745,6 +755,85 @@ 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)
@@ -1592,10 +1678,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,
@@ -1610,14 +1696,14 @@ def updateBoxes(self, menu_has_changed):
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)
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