Skip to content

added conversion in the launcher #122

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

Merged
merged 11 commits into from
Jun 3, 2025
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ curl -fsSL https://raw.githubusercontent.com/Axenide/Ax-Shell/main/install.sh |
- `vte3`
- `webp-pixbuf-loader`
- `wl-clipboard`
- `wlinhibit`
- `python-currencyconvertor`
- Python dependencies:
- ijson
- numpy
Expand Down
171 changes: 171 additions & 0 deletions modules/launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
from gi.repository import Gdk, GLib

import config.data as data
import json
import os
import re
import math
import numpy as np
import subprocess
from utils.conversion import Conversion
import modules.icons as icons
from modules.dock import Dock

Expand All @@ -38,12 +45,21 @@ def __init__(self, **kwargs):
self._arranger_handler: int = 0
self._all_apps = get_desktop_applications()


self.converter = Conversion()
self.calc_history_path = f"{data.CACHE_DIR}/calc.json"
if os.path.exists(self.calc_history_path):
with open(self.calc_history_path, "r") as f:
self.calc_history = json.load(f)
else:
self.calc_history = []

self.conversion_history_path = f"{data.CACHE_DIR}/conversion.json"
if os.path.exists(self.conversion_history_path):
with open(self.conversion_history_path, "r") as f:
self.conversion_history = json.load(f)
else:
self.conversion_history = []

self.viewport = Box(name="viewport", spacing=4, orientation="v")
self.search_entry = Entry(
Expand Down Expand Up @@ -140,6 +156,10 @@ def arrange_viewport(self, query: str = ""):

self.update_calculator_viewport()
return
if query.startswith(";"):
# In conversion mode, update history view once (not per keystroke)
self.update_conversion_viewport()
return
remove_handler(self._arranger_handler) if self._arranger_handler else None
self.viewport.children = []
self.selected_index = -1
Expand Down Expand Up @@ -262,6 +282,11 @@ def scroll():
def on_search_entry_activate(self, text):
if text.startswith("="):

if self.selected_index == -1:
self.evaluate_calculator_expression(text)
return
if text.startswith(";"):
# If in calculator mode and no history item is selected, evaluate new expression.
if self.selected_index == -1:
self.evaluate_calculator_expression(text)
return
Expand Down Expand Up @@ -315,6 +340,35 @@ def on_search_entry_key_press(self, widget, event):
self.close_launcher()
return True
return False
if text.startswith(";"):
if event.keyval == Gdk.KEY_Down:
self.move_selection(1)
return True
elif event.keyval == Gdk.KEY_Up:
self.move_selection(-1)
return True
elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
# In conversion mode, if a history item is highlighted:
if self.selected_index != -1 and self.selected_index < len(self.conversion_history):
if event.state & Gdk.ModifierType.SHIFT_MASK:
# Shift+Enter deletes the selected calculator history item
self.delete_selected_conversion_history()
else:
# Normal Enter copies the result
selected_text = self.conversion_history[self.selected_index]
self.copy_text_to_clipboard(selected_text)
# Clear selection so new expressions are evaluated on further Return presses
self.selected_index = -1
else:
# Force reset selection index
self.selected_index = -1
# No item selected, evaluate the expression
self.evaluate_conversion_expression(text)
return True
elif event.keyval == Gdk.KEY_Escape:
self.close_launcher()
return True
return False
else:

if event.keyval == Gdk.KEY_Down:
Expand All @@ -338,6 +392,10 @@ def notify_text(self, entry, *_):
if text.startswith("="):
self.update_calculator_viewport()

self.selected_index = -1
elif text.startswith(";"):
self.update_conversion_viewport()
# Always reset selection when typing a new expression
self.selected_index = -1
else:
self.arrange_viewport(text)
Expand Down Expand Up @@ -410,6 +468,10 @@ def save_calc_history(self):
with open(self.calc_history_path, "w") as f:
json.dump(self.calc_history, f)

def save_conversion_history(self):
with open(self.conversion_history_path, "w") as f:
json.dump(self.conversion_history, f)

def evaluate_calculator_expression(self, text: str):

print(f"Evaluating calculator expression: {text}")
Expand Down Expand Up @@ -484,6 +546,27 @@ def evaluate_calculator_expression(self, text: str):
self.save_calc_history()
self.update_calculator_viewport()

def evaluate_conversion_expression(self, text: str):
print(f"Evaluating conversion expression: {text}")
expr = text.lstrip(";").strip()
if not expr:
return

try:
result_value, result_type = self.converter.parse_input_and_convert(expr)
if result_type is None:
result_str = f"{result_value:.2f}"
else:
result_str = f"{result_value:.2f} {result_type}"
except:
result_str = "Error: Invalid conversion expression"

# Format the result based on its type

self.conversion_history.insert(0, f"{text} => {result_str}")
self.save_conversion_history()
self.update_conversion_viewport()

def update_calculator_viewport(self):
self.viewport.children = []
for item in self.calc_history:
Expand All @@ -492,6 +575,16 @@ def update_calculator_viewport(self):

if self.selected_index >= len(self.calc_history):
self.selected_index = -1

def update_conversion_viewport(self):
self.viewport.children = []
for item in self.conversion_history:
btn = self.create_conversion_history_button(item)
self.viewport.add(btn)
# Don't reset selection index here automatically
# Ensure selection state stays valid
if self.selected_index >= len(self.conversion_history):
self.selected_index = -1

def create_calc_history_button(self, text: str) -> Button:

Expand Down Expand Up @@ -547,6 +640,60 @@ def create_calc_history_button(self, text: str) -> Button:
)
return btn

def create_conversion_history_button(self, text: str) -> Button:
# Parse the result to create a more readable display
if "=>" in text:
parts = text.split("=>")
expression = parts[0].strip()
result = parts[1].strip()

# For very long results, truncate for display but keep full in tooltip
display_text = text
if len(result) > 50: # Truncate long results
display_text = f"{expression} => {result[:47]}..."

btn = Button(
name="slot-button", # reuse existing CSS styling
child=Box(
name="calc-slot-box",
orientation="h",
spacing=10,
children=[
Label(
name="calc-label",
label=display_text,
ellipsization="end",
v_align="center",
h_align="center",
),
],
),
tooltip_text=text,
on_clicked=lambda *_: self.copy_text_to_clipboard(text),
)
else:
# Fallback for non-calculation entries
btn = Button(
name="slot-button",
child=Box(
name="calc-slot-box",
orientation="h",
spacing=10,
children=[
Label(
name="calc-label",
label=text,
ellipsization="end",
v_align="center",
h_align="center",
),
],
),
tooltip_text=text,
on_clicked=lambda *_: self.copy_text_to_clipboard(text),
)
return btn

def copy_text_to_clipboard(self, text: str):

parts = text.split("=>", 1)
Expand Down Expand Up @@ -577,3 +724,27 @@ def delete_selected_calc_history(self):

if len(self.calc_history) > 0:
self.update_selection(min(new_index, len(self.calc_history) - 1))

def delete_selected_conversion_history(self):
if self.selected_index != -1 and self.selected_index < len(self.conversion_history):
# Store the current index before deletion
current_index = self.selected_index

# Delete the item
del self.conversion_history[current_index]
self.save_conversion_history()

# Determine the new selection index
# If we deleted the first item, stay at index 0
# Otherwise, move to the previous item
new_index = 0 if current_index == 0 else current_index - 1

# Reset selection before updating viewport
self.selected_index = -1

# Update the viewport
self.update_conversion_viewport()

# If we still have items, select the determined index
if len(self.conversion_history) > 0:
self.update_selection(min(new_index, len(self.conversion_history) - 1))
Loading