Skip to content

Commit

Permalink
cs_keyboard.py and KeybindingWidgets.py: Add support for search by bi…
Browse files Browse the repository at this point in the history
…nding

This implements an additional option that enables searching by a
specific keyboard binding. The user input is a single binding sequence
and the search recursively finds existing matching entries and displays
results similar to the search by shortcut name, including markup.
  • Loading branch information
rcalixte committed May 13, 2024
1 parent 2cb9afc commit 058afbc
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ def __init__(self, a_widget, accel_string=None):
self.path = None
self.press_event = None
self.teaching = False
self.default_value = True
self.text_string = ""

self.update_label()

Expand All @@ -177,7 +179,7 @@ def do_set_property(self, prop, value):
raise AttributeError('unknown property %s' % prop.name)

def update_label(self):
text = _("unassigned")
text = _("unassigned") if self.default_value else self.text_string
if self.accel_string:
restore_atab = False
valid = self.accel_string
Expand Down Expand Up @@ -295,7 +297,7 @@ def on_key_release(self, widget, event):

# print("accel_mods: %d, keyval: %d, Storing %s as %s" % (accel_mods, keyval, accel_label, accel_string))

if (accel_mods == 0 or accel_mods == Gdk.ModifierType.SHIFT_MASK) and event.hardware_keycode != 0:
if (accel_mods == 0 or accel_mods == Gdk.ModifierType.SHIFT_MASK) and event.hardware_keycode != 0 and self.default_value:
if ((keyval >= Gdk.KEY_a and keyval <= Gdk.KEY_z)
or (keyval >= Gdk.KEY_A and keyval <= Gdk.KEY_Z)
or (keyval >= Gdk.KEY_0 and keyval <= Gdk.KEY_9)
Expand Down
177 changes: 140 additions & 37 deletions files/usr/share/cinnamon/cinnamon-settings/modules/cs_keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
gi.require_version("Gtk", "3.0")
from gi.repository import Gdk, Gio, Gtk

from KeybindingWidgets import CellRendererKeybinding
from KeybindingWidgets import ButtonKeybinding, CellRendererKeybinding
from SettingsWidgets import SidePage
from bin import util
from xapp.GSettingsWidgets import *
Expand Down Expand Up @@ -257,6 +257,8 @@ def __init__(self, content_box):
self.kb_search_handler_id = None
self.add_custom_button = None
self.remove_custom_button = None
self.search_choice = "shortcuts"
self.last_accel_string = ""

def on_module_selected(self):
if not self.loaded:
Expand Down Expand Up @@ -309,16 +311,50 @@ def on_module_selected(self):

left_vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2)
right_vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2)
self.search_vbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 2)

paned.add1(left_vbox)

right_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
paned.add2(right_box)

self.kb_search_entry = Gtk.Entry(placeholder_text=_("Type to search"))
right_box.pack_start(self.kb_search_entry, False, False, 2)
# Text entry search field
self.kb_search_entry = Gtk.Entry(placeholder_text=_("Type to search shortcuts"))
self.kb_search_handler_id = self.kb_search_entry.connect("changed", self.on_kb_search_changed)

# Binding entry search field
self.kb_search_frame = Gtk.Frame()
self.kb_search_frame.set_no_show_all(True)
self.kb_search_binding = ButtonKeybinding()
self.kb_search_binding.keybinding_cell.default_value = False
tooltip_text = _("Press Escape or click again to cancel the search.")
self.kb_search_binding.set_tooltip_text(tooltip_text)
text_string = _("Click to search by accelerator")
self.kb_search_binding.keybinding_cell.text_string = text_string
self.kb_search_binding.connect('accel-edited', self.onSearchBindingChanged)
self.kb_search_binding.connect('accel-cleared', self.onSearchBindingCleared)
self.kb_search_frame.add(self.kb_search_binding)

# Search option dropdown
self.kb_search_type = Gtk.ComboBox()
options = [(_("Shortcuts"), "shortcuts"),
(_("Bindings"), "bindings")]
model = Gtk.ListStore(str, str)
for option in options:
model.append(option)
self.kb_search_type.set_model(model)
cell = Gtk.CellRendererText()
self.kb_search_type.pack_start(cell, False)
self.kb_search_type.add_attribute(cell, "text", 0)
self.kb_search_type.set_active(0)
self.kb_search_type.connect("changed", self.on_kb_search_type_changed)

# Search menu
self.search_vbox.pack_start(self.kb_search_entry, True, True, 2)
self.search_vbox.pack_start(self.kb_search_frame, True, True, 2)
self.search_vbox.pack_end(self.kb_search_type, False, False, 2)
right_box.pack_start(self.search_vbox, False, False, 2)

right_scroller = Gtk.ScrolledWindow.new(None, None)
right_scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
right_scroller.add(right_vbox)
Expand Down Expand Up @@ -545,7 +581,7 @@ def on_module_selected(self):
longest_cat_label = " "

for category in self.main_store:
if category.parent is None:
if not category.parent:
cat_iters[category.int_name] = self.cat_store.append(None)
else:
cat_iters[category.int_name] = self.cat_store.append(cat_iters[category.parent])
Expand Down Expand Up @@ -580,7 +616,7 @@ def on_module_selected(self):
except:
widget = None

if widget is not None:
if widget:
cheat_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2)
cheat_box.pack_start(widget, True, True, 2)
cheat_box.set_vexpand(False)
Expand All @@ -606,6 +642,10 @@ def onCategorySelected(self, tree, event=None):

model, tree_iter = tree.get_selection().get_selected()
category = model.get_value(tree_iter, 2) if tree_iter else None
if category and self.search_choice == "bindings":
self.onSearchBindingCleared(None)
self.categoryHighlightOnMap()

if not category or not category.properties:
self.categoryHighlightUnmap()
return
Expand All @@ -628,7 +668,7 @@ def onCategoryChanged(self, tree):
self.kb_search_entry.set_text("")
self.kb_search_entry.handler_unblock(self.kb_search_handler_id)

if tree.get_selection() is not None:
if tree.get_selection():
categories, tree_iter = tree.get_selection().get_selected()
if tree_iter:
category = categories.get_value(tree_iter, 2)
Expand All @@ -645,6 +685,28 @@ def on_kb_search_changed(self, entry, data=None):
self.last_selected_binding = None
self.kb_store.refilter()

def on_kb_search_type_changed(self, entry):
model = self.kb_search_type.get_model()
option = self.kb_search_type.get_active()
self.search_choice = model[option][1]

if self.search_choice == "shortcuts":
self.onSearchBindingCleared(None)
self.kb_search_frame.hide()
self.kb_search_binding.hide()
self.kb_search_entry.show()
else:
self.kb_search_entry.handler_block(self.kb_search_handler_id)
self.kb_search_entry.set_text("")
self.kb_search_entry.handler_unblock(self.kb_search_handler_id)
self.kb_store.refilter()
self.kb_search_entry.hide()
self.kb_search_binding.show()
self.kb_search_frame.show()

self.bindingHighlightOnUnmap()
self.categoryHighlightUnmap()

def populate_kb_tree(self):
self.kb_root_store.clear()
self.current_category = None
Expand All @@ -660,29 +722,42 @@ def populate_kb_tree(self):
def kb_name_cell_data_func(self, column, cell, model, tree_iter, data=None):
binding = model.get_value(tree_iter, 1)

if binding is not None:
if self.kb_search_entry.get_text() != "":
category = escape(self.binding_categories[binding.category])
__, *num = binding.category.split("_")
_id = f" {num[0]}" if num else ""
label = escape(binding.label)
markup = f"<span font_weight='ultra-light'>({category}{_id})</span> {label}"
cell.set_property("markup", markup)
else:
cell.set_property("text", binding.label)
if binding and self.kb_search_entry.get_text() or self.kb_search_binding.get_accel_string():
category = escape(self.binding_categories[binding.category])
__, *num = binding.category.split("_")
_id = f" {num[0]}" if num else ""
label = escape(binding.label)
markup = f"<span font_weight='ultra-light'>({category}{_id})</span> {label}"
cell.set_property("markup", markup)
else:
cell.set_property("text", binding.label)

def kb_store_visible_func(self, model, tree_iter, data=None):
if self.current_category is None and self.kb_search_entry.get_text() == "":
return False
if self.search_choice == "shortcuts":
if not self.current_category and not self.kb_search_entry.get_text():
return False

keybinding = self.kb_root_store.get_value(tree_iter, 1)
keybinding = self.kb_root_store.get_value(tree_iter, 1)

search = self.kb_search_entry.get_text().lower().strip()
if search:
return search in keybinding.label.lower().strip()

search = self.kb_search_entry.get_text().lower().strip()
if search != "":
return search in keybinding.label.lower().strip()
if self.current_category and hasattr(self.current_category, 'int_name'):
return keybinding.category == self.current_category.int_name
else:
if not self.current_category and not self.kb_search_binding.get_accel_string():
return False

if self.current_category is not None:
return keybinding.category == self.current_category.int_name
keybinding = self.kb_root_store.get_value(tree_iter, 1)

search = self.kb_search_binding.get_accel_string()
if search:
entries = [Gtk.accelerator_parse_with_keycode(entry) for entry in keybinding.entries]
return Gtk.accelerator_parse_with_keycode(search) in entries

if self.current_category and hasattr(self.current_category, 'int_name'):
return keybinding.category == self.current_category.int_name

def loadCustoms(self):
tree_iter = self.kb_root_store.get_iter_first()
Expand Down Expand Up @@ -717,15 +792,28 @@ def loadCustoms(self):

def onKeyBindingChanged(self, tree):
self.entry_store.clear()
if tree.get_selection() is not None:

if tree.get_selection():
keybindings, tree_iter = tree.get_selection().get_selected()
if tree_iter and self.search_choice == "bindings" and self.kb_search_binding.accel_string:
pass
elif tree_iter and self.search_choice == "bindings" and self.last_accel_string and self.kb_search_binding.accel_string:
if self.last_accel_string != self.kb_search_binding.accel_string:
self.last_accel_string = self.kb_search_binding.accel_string
self.kb_search_binding.keybinding_cell.set_value(None)
self.kb_search_binding.accel_string = ""
self.kb_search_binding.load_model()

if tree_iter:
keybinding = keybindings.get_value(tree_iter, 1)
for entry in keybinding.entries:
if entry != "_invalid_":
self.entry_store.append((entry,))
self.remove_custom_button.set_property('sensitive', isinstance(keybinding, CustomKeyBinding))

if self.search_choice == "bindings" and self.kb_search_binding.accel_string:
self.last_accel_string = self.kb_search_binding.accel_string

def onEntryChanged(self, cell, path, accel_string, accel_label, entry_store):
keybindings, kb_iter = self.kb_tree.get_selection().get_selected()
if kb_iter:
Expand Down Expand Up @@ -769,6 +857,18 @@ def onEntryCleared(self, cell, path, entry_store):
self.onKeyBindingChanged(self.kb_tree)
self.entry_tree.get_selection().select_path(path)

def onSearchBindingChanged(self, cell, path, accel_string):
self.kb_store.refilter()
self.current_category = None
self.cat_tree.get_selection().unselect_all()
self.bindingHighlightOff()

def onSearchBindingCleared(self, cell):
self.kb_search_binding.keybinding_cell.set_value(None)
self.kb_search_binding.accel_string = ""
self.kb_search_binding.load_model()
self.kb_store.refilter()

def onAddCustomButtonClicked(self, button):
dialog = AddCustomDialog(False)

Expand Down Expand Up @@ -883,8 +983,14 @@ def onContextMenuPopup(self, tree, event=None):
return
model, tree_iter = tree.get_selection().get_selected()
binding = model.get_value(tree_iter, 1) if tree_iter else None

if self.last_selected_binding and binding and hasattr(binding, "properties") and self.last_selected_binding != binding.properties:
self.bindingHighlightOnUnmap()

search_input_text = False
if self.kb_search_entry.get_text():
search_input_text = True

if binding and hasattr(binding, "category"):
for index, category in enumerate(self.cat_store):
if category[2].int_name == binding.category:
Expand All @@ -911,6 +1017,9 @@ def onContextMenuPopup(self, tree, event=None):
self.bindingHighlightOnMap()

if tree_iter:
if search_input_text or self.kb_search_binding.accel_string:
self.onSearchBindingCleared(None)
return
if isinstance(binding, CustomKeyBinding):
return
if event:
Expand All @@ -919,7 +1028,7 @@ def onContextMenuPopup(self, tree, event=None):
button = event.button
event_time = event.time
info = tree.get_path_at_pos(int(event.x), int(event.y))
if info is not None:
if info:
path, col, *__ = info
tree.grab_focus()
tree.set_cursor(path, col, 0)
Expand Down Expand Up @@ -1062,16 +1171,13 @@ def get_array(self, raw_array):
return result

def setBinding(self, index, val):
if val is not None:
self.entries[index] = val
else:
self.entries[index] = ""
self.entries[index] = val if val else ""
self.writeSettings()

def writeSettings(self):
array = []
for entry in self.entries:
if entry != "":
if entry:
array.append(entry)

if "/" not in self.schema:
Expand Down Expand Up @@ -1118,10 +1224,7 @@ def get_array(self, raw_array):
return result

def setBinding(self, index, val):
if val is not None:
self.entries[index] = val
else:
self.entries[index] = ""
self.entries[index] = val if val else ""
self.writeSettings()

def writeSettings(self):
Expand All @@ -1133,7 +1236,7 @@ def writeSettings(self):

array = []
for entry in self.entries:
if entry != "":
if entry:
array.append(entry)
settings.set_strv("binding", array)

Expand Down Expand Up @@ -1181,5 +1284,5 @@ def onFilePicked(self, widget):
self.command_entry.set_text(file.get_path().replace(" ", r"\ "))

def onEntriesChanged(self, widget):
ok_enabled = self.name_entry.get_text().strip() != "" and self.command_entry.get_text().strip() != ""
ok_enabled = self.name_entry.get_text().strip() and self.command_entry.get_text().strip()
self.set_response_sensitive(Gtk.ResponseType.OK, ok_enabled)

0 comments on commit 058afbc

Please sign in to comment.