Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix search and add fuzzy search option #282

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 108 additions & 22 deletions usr/lib/linuxmint/mintMenu/plugins/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions usr/lib/linuxmint/mintMenu/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions usr/share/glib-2.0/schemas/com.linuxmint.mintmenu.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@
<description></description>
</key>

<key type="b" name="enable-fuzzy-search">
<default>false</default>
<summary></summary>
<description></description>
</key>

<key type="b" name="search-on-top">
<default>false</default>
<summary></summary>
Expand Down