roundabout,
created on Monday, 9 December 2024, 17:56:31 (1733766991),
received on Thursday, 12 December 2024, 14:59:13 (1734015553)
Author identity: vlad <vlad.muntoiu@gmail.com>
76e49229b6f7b8eaab55049e74074291e3140490
izvor.ui
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkMenu" id="main-menu">
@@ -23,15 +23,6 @@
<signal name="activate" handler="menu-preferences" swapped="no"/>
</object>
</child>
<child>
<object class="GtkMenuItem">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Manage _history</property>
<property name="use-underline">True</property>
<signal name="activate" handler="menu-history" swapped="no"/>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
@@ -68,6 +59,7 @@
</object>
</child>
</object>
<object class="GtkEntryBuffer" id="search-query-buffer"/>
<object class="GtkApplicationWindow" id="root">
<property name="can-focus">False</property>
<property name="default-width">576</property>
@@ -171,5 +163,4 @@
</object>
</child>
</object>
<object class="GtkEntryBuffer" id="search-query-buffer"/>
</interface>
main.py
@@ -24,14 +24,17 @@ import gbulb
from pathlib import Path
from typing import AsyncIterable, AsyncIterator, Collection, TypeVar, Iterable, Callable, Any
from xdg.Config import icon_size
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Pango, GLib
import gettext
import json
_ = gettext.gettext
_T = TypeVar("_T")
ICON_SIZE = 32
async def merge_generators_with_params(generator_funcs: list[Callable[..., AsyncIterable]],
*args, **kwargs):
@@ -52,7 +55,7 @@ async def merge_generators_with_params(generator_funcs: list[Callable[..., Async
class ResultInfoWidget(Gtk.ListBoxRow):
def __init__(self, name: str, description: str, image: tuple[str, Any], execute: Callable[[], None]):
def __init__(self, name: str, description: str, image: tuple[str, Any], execute: Callable[[], None], icon_size=32, show_description=True):
super().__init__()
self.name = name
self.description = description
@@ -62,10 +65,12 @@ class ResultInfoWidget(Gtk.ListBoxRow):
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
self.add(self.box)
if image[0] == "logo":
self.image = Gtk.Image.new_from_icon_name(self.image[1], Gtk.IconSize.LARGE_TOOLBAR)
self.image.set_pixel_size(ICON_SIZE)
self.box.pack_start(self.image, False, False, 0)
if icon_size:
# If the icon size is 0, have no icons
if image[0] == "logo":
self.image = Gtk.Image.new_from_icon_name(self.image[1], Gtk.IconSize.LARGE_TOOLBAR)
self.image.set_pixel_size(icon_size)
self.box.pack_start(self.image, False, False, 0)
self.label_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.box.pack_start(self.label_box, True, True, 0)
@@ -79,12 +84,12 @@ class ResultInfoWidget(Gtk.ListBoxRow):
self.name_label.set_attributes(attributes)
self.label_box.pack_start(self.name_label, True, True, 0)
self.description_label = Gtk.Label(label=self.description)
self.description_label.set_line_wrap(True)
self.description_label.set_justify(Gtk.Justification.LEFT)
self.description_label.set_halign(Gtk.Align.START)
self.label_box.pack_start(self.description_label, True, True, 0)
if show_description:
self.description_label = Gtk.Label(label=self.description)
self.description_label.set_line_wrap(True)
self.description_label.set_justify(Gtk.Justification.LEFT)
self.description_label.set_halign(Gtk.Align.START)
self.label_box.pack_start(self.description_label, True, True, 0)
class Izvor(Gtk.Application):
USER_CONFIGS = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "izvor"
@@ -96,6 +101,7 @@ class Izvor(Gtk.Application):
SYSTEM_PROVIDERS = SYSTEM_DATA / "providers"
SYSTEM_PROVIDER_CONFIGS = SYSTEM_CONFIGS / "providers"
ENABLED_PROVIDERS_FILE = USER_CONFIGS / "enabled_providers"
APPEARANCE_CONFIG_FILE = USER_CONFIGS / "appearance.json"
def __init__(self):
super().__init__(application_id="com.roundabout-host.roundabout.izvor")
@@ -106,7 +112,7 @@ class Izvor(Gtk.Application):
self.window = self.builder.get_object("root")
self.window.connect("destroy", self.kill)
self.window.connect("key-press-event", self.check_escape)
self.window.set_title("Izvor")
self.window.set_title(_("Launcher"))
if not os.getenv("XDG_DATA_HOME"):
print("XDG_DATA_HOME is not set. Using default path.")
@@ -141,6 +147,16 @@ class Izvor(Gtk.Application):
for provider in self.available_providers:
f.write(provider + "\n")
if not self.APPEARANCE_CONFIG_FILE.exists():
self.APPEARANCE_CONFIG_FILE.touch()
with open(self.APPEARANCE_CONFIG_FILE, "w") as f:
json.dump({"icon_size": 32, "show_result_descriptions": True}, f)
with open(self.APPEARANCE_CONFIG_FILE, "r") as f:
appearance_config = json.load(f)
self.icon_size = appearance_config.get("icon_size", 32)
self.show_result_descriptions = appearance_config.get("show_result_descriptions", True)
self.permanently_enabled_providers = {}
with open(self.ENABLED_PROVIDERS_FILE, "r") as f:
@@ -170,8 +186,12 @@ class Izvor(Gtk.Application):
self.provider_checkboxes[provider.stem].show()
self.provider_checkboxes[provider.stem].connect("toggled", self.update_enabled_providers)
self.update_task = None
def call_update_results(widget):
asyncio.create_task(self.update_results(widget))
if self.update_task is not None:
self.update_task.cancel()
self.update_task = asyncio.create_task(self.update_results(widget))
def execute_result(widget, row):
row.execute()
@@ -184,6 +204,7 @@ class Izvor(Gtk.Application):
"providers-menu-all-toggled": self.update_enabled_providers_all,
"menu-about": self.about,
"menu-providers": self.provider_menu,
"menu-preferences": self.preferences,
}
)
@@ -196,7 +217,9 @@ class Izvor(Gtk.Application):
else:
self.enabled_providers.pop(provider, None)
asyncio.create_task(self.update_results(None))
if self.update_task is not None:
self.update_task.cancel()
self.update_task = asyncio.create_task(self.update_results(widget))
def update_enabled_providers_all(self, widget):
for checkbox in self.provider_checkboxes.values():
@@ -243,10 +266,15 @@ class Izvor(Gtk.Application):
self.scroll_to_row(rows[prev_index], results_list.get_adjustment())
return True
if event.keyval == Gdk.KEY_Return and current_row:
# Execute the selected row
current_row.activate()
return True
if event.keyval == Gdk.KEY_Return:
if current_row:
# Execute the selected row
current_row.activate()
return True
elif len(rows) > 0:
# Execute the first row
rows[0].activate()
return True
return False
@@ -301,7 +329,7 @@ class Izvor(Gtk.Application):
provider_description.set_line_wrap(True)
icon = Gtk.Image.new_from_icon_name(loaded_provider.icon, Gtk.IconSize.LARGE_TOOLBAR)
icon.set_pixel_size(ICON_SIZE)
icon.set_pixel_size(self.icon_size)
switch = Gtk.Switch()
switch.set_valign(Gtk.Align.CENTER)
@@ -320,6 +348,31 @@ class Izvor(Gtk.Application):
providers_window.show_all()
def preferences(self, widget):
preferences_builder = Gtk.Builder()
preferences_builder.add_from_file("preferences.ui")
preferences_window = preferences_builder.get_object("preferences-window")
preferences_window.connect("destroy", lambda _: preferences_window.destroy())
def update_appearance_preferences(widget, *args):
self.icon_size = preferences_builder.get_object("icon-size").get_value()
self.show_result_descriptions = preferences_builder.get_object("show-result-descriptions").get_active()
with open(self.APPEARANCE_CONFIG_FILE, "w") as f:
json.dump({"icon_size": self.icon_size, "show_result_descriptions": self.show_result_descriptions}, f)
# Regenerate the results to reflect the changes
if self.update_task is not None:
self.update_task.cancel()
self.update_task = asyncio.create_task(self.update_results(widget))
preferences_builder.connect_signals(
{
"update-appearance": update_appearance_preferences,
}
)
preferences_window.show_all()
def update_permanently_enabled_providers(self, widget, param, provider):
if widget.get_active():
@@ -333,6 +386,8 @@ class Izvor(Gtk.Application):
async def update_results(self, widget):
print("Updating results...")
spinner = self.builder.get_object("spinner")
spinner.start()
generators = []
query = self.builder.get_object("search-query-buffer").get_text()
# print(f"Query: {query}")
@@ -352,11 +407,12 @@ class Izvor(Gtk.Application):
async for result in merge_generators_with_params(generators, query):
# print(result)
result_box = ResultInfoWidget(result["name"], result["description"], result["image"], result["execute"])
result_box = ResultInfoWidget(result["name"], result["description"], result["image"], result["execute"], self.icon_size, self.show_result_descriptions)
results_list.add(result_box)
result_box.show_all()
results_list.show_all()
spinner.stop()
if __name__ == "__main__":
preferences.ui
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkAdjustment" id="icon-size">
<property name="upper">128</property>
<property name="value">32</property>
<property name="step-increment">1</property>
<property name="page-increment">16</property>
<signal name="value-changed" handler="update-appearance" swapped="no"/>
</object>
<object class="GtkWindow" id="preferences-window">
<property name="can-focus">False</property>
<property name="title" translatable="yes">Preferences</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkStackSidebar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stack">stack</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkStack" id="stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="transition-type">slide-up-down</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">4</property>
<property name="margin-end">4</property>
<property name="margin-top">4</property>
<property name="margin-bottom">4</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Show result descriptions</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSwitch" id="show-result-descriptions">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="active">True</property>
<signal name="state-set" handler="update-appearance" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">4</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Icon size</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="icon-size-button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="adjustment">icon-size</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="name">appearance</property>
<property name="title" translatable="yes">Appearance</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>