Move the search button to the right upper corner of the results field instead of the upper middle. It should still overlay the results field. Also the search bar should also overlay the results field and open to the right of the button with the right border of the results field as it boundary the button should move accordingly when the search field opens and closes. Print the new code in full. Python 3.12 code: import math
import re
from typing import Any, Union, Callable
from math import ceil

from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLineEdit, QTextEdit, QScrollBar, QPushButton, QLabel, QFrame,
QGridLayout
)
from PySide6.QtGui import QFont
from PySide6.QtCore import Qt, QSize
-- Utility Functions --

def convert_angle(func: Callable[[float], float], x: float, use_degrees: bool) -> float:
"""Convert angle based on degree mode."""
return func(math.radians(x) if use_degrees else x)
-- Conversions Category --

def kilograms_to_pounds(kg: float) -> float:
"""Convert kilograms to pounds."""
return kg / 0.45359237

def pounds_to_kilograms(lb: float) -> float:
"""Convert pounds to kilograms."""
return lb * 0.45359237

def celsius_to_kelvin(c: float) -> float:
"""Convert Celsius to Kelvin."""
return c + 273.15

def kelvin_to_celsius(k: float) -> float:
"""Convert Kelvin to Celsius."""
return k - 273.15

def celsius_to_fahrenheit(c: float) -> float:
"""Convert Celsius to Fahrenheit."""
return c * 9/5 + 32

def fahrenheit_to_celsius(f: float) -> float:
"""Convert Fahrenheit to Celsius."""
return (f - 32) * 5/9
-- Text Category --

def capitalize_sentence(sentence: str) -> str:
"""Capitalize the first letter of each word in the sentence."""
return ' '.join(word.capitalize() for word in sentence.split())

def remove_duplicates(text: str) -> str:
"""Remove duplicate words and empty lines from a string."""
words = ' '.join(line.strip() for line in text.splitlines() if line.strip()).split()
return ' '.join(dict.fromkeys(words))
-- Processing --

def get_safe_globals(use_degrees: bool, variables: dict) -> dict:
"""Return a dictionary of safe globals for expression evaluation, including user-defined variables."""
trig_funcs = {'sin', 'cos', 'tan', 'asin', 'acos', 'atan'}
safe_globals = {
"sqrt": math.sqrt,
"log": math.log10,
"ln": math.log,
"pi": math.pi,
"e": math.e,
"lb": kilograms_to_pounds,
"kg": pounds_to_kilograms,
"C": kelvin_to_celsius,
"K": celsius_to_kelvin,
"F": celsius_to_fahrenheit,
"CF": fahrenheit_to_celsius,
"capitalize": capitalize_sentence,
"duplicates": remove_duplicates,
}
Add trigonometric functions with angle conversion

for func in trig_funcs:
safe_globals[func] = (lambda f: (lambda x: convert_angle(math.dict[f], x, use_degrees)))(func)
Include user-defined variables

safe_globals.update(variables)
return safe_globals

def preprocess_text_functions(expression: str) -> str:
"""Preprocess text functions to ensure proper quoting of arguments."""
def add_quotes(match):
func, args = match.groups()
return f'{func}("{args}")' if func in ['capitalize', 'duplicates'] else match.group(0)
Corrected regex pattern with only two capturing groups

return re.sub(r'\b(capitalize|duplicates)(([^)]+))', add_quotes, expression)

def validate_inverse_trig(expression: str, safe_globals: dict) -> Union[str, None]:
"""Validate inputs for inverse trigonometric functions."""
for func, arg in re.findall(r"(\b\w+\b)(([^)]+))", expression):
if func in ["asin", "acos"]:
try:
value = eval(arg, {"builtins": None}, safe_globals)
if abs(value) > 1:
return f"Error: {func}(x) is undefined for |x| > 1"
except Exception as e:
return f"Error: {e}"
return None

def safe_evaluate(expression: str, safe_globals: dict, use_e_notation: bool) -> Union[str, Any]:
"""Safely evaluate the expression and format the result."""
try:
result = eval(expression, {"builtins": None}, safe_globals)
if isinstance(result, float) and abs(result) < 1e-10:
result = 0.0
if isinstance(result, (int, float)):
if use_e_notation:
Format using scientific notation with up to 2 decimal places

result = f"{result:.2e}"
else:
Standard formatting with rounding

result = round(result, 2)
return result
except Exception as e:
return f"Error: {e}"

def evaluate_expression(expression: str, variables: dict, use_degrees: bool = False, use_e_notation: bool = False) -> Union[float, str]:
"""Safely evaluate a mathematical expression considering degree mode and handle variable assignments."""
Check for variable assignment

assignment_match = re.match(r'^\s*([a-zA-Z_]\w*)\s*=\s*(.+)$', expression)
if assignment_match:
var_name, var_expr = assignment_match.groups()
Evaluate the expression for the variable

safe_globals = get_safe_globals(use_degrees, variables)
var_value = safe_evaluate(var_expr, safe_globals, use_e_notation)
if isinstance(var_value, (int, float)):
variables[var_name] = var_value
return f"{var_name} = {var_value}"
elif isinstance(var_value, str) and var_value.startswith("Error"):
return var_value
else:
return f"{var_name} = {var_value}"
else:
Regular expression evaluation

safe_globals = get_safe_globals(use_degrees, variables)
expression = expression.replace("^", "**")
expression = preprocess_text_functions(expression)
error = validate_inverse_trig(expression, safe_globals)
if error:
return error
return safe_evaluate(expression, safe_globals, use_e_notation)
-- GUI --

class CalculatorGUI(QMainWindow):
"""PySide6-based Calculator with Degrees/Radians toggle, Variables, History, and Search."""

def init(self):
super().init()
self.use_degrees = False
self.use_e_notation = False
self.variables = {} # Dictionary to store variables
self.history_entries = [] # List to store history entries
self.setup_window()
self.create_widgets()
self.layout_widgets()

def setup_window(self) -> None:
"""Configure the main application window."""
self.setWindowTitle("Calculator")
self.setGeometry(100, 100, 1280, 720)

def create_widgets(self) -> None:
"""Initialize all GUI components."""
font = QFont()
font.setPointSize(12)
font.setFamily('Literata')

# Entry field
self.entry = QLineEdit()
self.entry.setFont(font)
self.entry.returnPressed.connect(self.calculate)
self.entry.setPlaceholderText('Enter expression')
self.entry.setClearButtonEnabled(True)

# History text area
self.history_text = QTextEdit()
self.history_text.setFont(font)
self.history_text.setReadOnly(True)
self.history_text.setStyleSheet("")  # Removed padding-right to eliminate vertical bar

# Scrollbar for history
self.scrollbar = QScrollBar(Qt.Vertical)
self.history_text.setVerticalScrollBar(self.scrollbar)

# Create search button as overlay
self.search_button = QPushButton("🔍", self.history_text)
self.search_button.setFixedSize(24, 24)
self.search_button.setToolTip("Search History")
self.search_button.setStyleSheet("""
    QPushButton {
        border: none;
        background-color: rgba(255, 255, 255, 200); /* Semi-transparent background */
        font-size: 14px;
        border-radius: 12px;
    }
    QPushButton:hover {
        background-color: rgba(0, 120, 215, 200); /* Blue on hover */
        color: white;
    }
""")
self.search_button.clicked.connect(self.toggle_search)

# Position the search button at top-right corner
# Initial positioning; will be updated in resizeEvent
self.search_button.move(
    self.history_text.viewport().width() - self.search_button.width() - 5, 5
)

# Search bar (hidden initially)
self.search_bar = QLineEdit(self.history_text)
self.search_bar.setPlaceholderText("Search...")
self.search_bar.setFixedHeight(24)
self.search_bar.setVisible(False)
self.search_bar.setStyleSheet("""
    QLineEdit {
        border: 1px solid gray;
        border-radius: 4px;
        padding: 2px 4px;
        background-color: white;
    }
""")
self.search_bar.textChanged.connect(self.filter_history)

# Set initial size of search_bar to 25% of history_text's width
self.update_search_bar_position()

# Function buttons
self.function_buttons = {
    "Functions": ["sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "log", "ln"],
    "Conversions": ["lb", "kg", "K", "C", "F", "CF"],
    "Text": ["capitalize", "duplicates"],
    "Constants": ["pi", "e"],
    "Settings": []
}
self.function_frame = QFrame()
self.create_function_buttons(font)

def create_function_buttons(self, font: QFont) -> None:
"""Create buttons for inserting functions into the entry."""
layout = QGridLayout()

# Define categories and their column spans
categories = [("Functions", 3), ("Conversions", 2), ("Text", 2), ("Constants", 2), ("Settings", 1)]
starting_col = 0

for category, col_span in categories:
    # Add category label
    label = QLabel(category)
    label.setFont(font)
    layout.addWidget(label, 0, starting_col, 1, col_span, Qt.AlignCenter)

    buttons = self.function_buttons.get(category, [])
    if category == "Functions":
        for i, func in enumerate(buttons):
            button = QPushButton(func)
            button.setFont(font)
            button.clicked.connect(lambda _, f=func: self.insert_function(f, False))
            layout.addWidget(button, (i // 3) + 1, starting_col + (i % 3))

    elif category == "Settings":
        # Arrange Settings buttons vertically within a single column
        self.mode_button = QPushButton("Radians")
        self.mode_button.setFont(font)
        self.mode_button.clicked.connect(self.toggle_mode)
        layout.addWidget(self.mode_button, 1, starting_col, 1, 1)

        self.e_notation_button = QPushButton("E Notation: Off")
        self.e_notation_button.setFont(font)
        self.e_notation_button.clicked.connect(self.toggle_e_notation)
        layout.addWidget(self.e_notation_button, 2, starting_col, 1, 1)

    elif category == "Conversions":
        for idx, func in enumerate(buttons):
            if func == "lb":
                display_text = "lb(kg)"
            elif func == "kg":
                display_text = "kg(lb)"
            elif func == "K":
                display_text = "K(°C)"
            elif func == "C":
                display_text = "°C(K)"
            elif func == "F":
                display_text = "F(°C)"
            elif func == "CF":
                display_text = "°C(F)"
            else:
                display_text = func
            button = QPushButton(display_text)
            button.setFont(font)
            button.clicked.connect(lambda _, f=func: self.insert_function(f, False))
            row = 1 + (idx // col_span)
            col = starting_col + (idx % col_span)
            layout.addWidget(button, row, col)

    else:
        num_buttons = len(buttons)
        num_rows = ceil(num_buttons / col_span)

        for idx, func in enumerate(buttons):
            button_text = f"{func}"
            button = QPushButton(button_text)
            button.setFont(font)
            button.clicked.connect(lambda _, f=func, c=(category == "Constants"): self.insert_function(f, c))
            row = 1 + (idx // col_span)
            col = starting_col + (idx % col_span)
            layout.addWidget(button, row, col)

        for extra in range(col_span * num_rows - num_buttons):
            layout.addWidget(QLabel(""), 1 + (extra // col_span), starting_col + (extra % col_span))

    starting_col += col_span

self.function_frame.setLayout(layout)

def layout_widgets(self) -> None:
"""Arrange widgets within the window."""
central_widget = QWidget()
main_layout = QVBoxLayout()

# Add entry field
main_layout.addWidget(self.entry)

# Add history and scrollbar in a horizontal layout
history_layout = QHBoxLayout()
history_layout.addWidget(self.history_text)
history_layout.addWidget(self.scrollbar)
main_layout.addLayout(history_layout)

# Add function buttons frame
main_layout.addWidget(self.function_frame)

central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)

def toggle_mode(self) -> None:
"""Switch between Degrees and Radians modes."""
self.use_degrees = not self.use_degrees
self.mode_button.setText("Degrees" if self.use_degrees else "Radians")

def toggle_e_notation(self) -> None:
"""Toggle the E notation setting."""
self.use_e_notation = not self.use_e_notation
state = "On" if self.use_e_notation else "Off"
self.e_notation_button.setText(f"E Notation: {state}")

def insert_function(self, function: str, is_constant: bool = False) -> None:
"""Insert function or constant text into the expression entry."""
current_text = self.entry.text()
cursor_position = self.entry.cursorPosition()
if is_constant:
new_text = f"{current_text[:cursor_position]}{function}"
new_cursor_position = cursor_position + len(function)
else:
new_text = f"{current_text[:cursor_position]}{function}() {current_text[cursor_position:]}"
new_cursor_position = cursor_position + len(function) + 1
self.entry.setText(new_text)
self.entry.setCursorPosition(new_cursor_position)
self.entry.setFocus()

def calculate(self) -> None:
"""Evaluate the expression and update history."""
expression = self.entry.text().strip()
if expression:
result = evaluate_expression(
expression, self.variables, use_degrees=self.use_degrees, use_e_notation=self.use_e_notation
)
self.display_result(expression, result)
self.entry.clear()

def display_result(self, expression: str, result: Union[float, str]) -> None:
"""Append the calculation to the history log."""
mode = "D" if self.use_degrees else "R"
# Check if the expression is a variable assignment
if re.match(r'^\s*[a-zA-Z_]\w*\s*=\s*.+$', expression):
# For variable assignments, display only the expression
history_entry = f"[{mode}] {result}"
else:
# For regular expressions, display expression and result
history_entry = f"[{mode}] {expression} = {result}"
self.history_entries.append(history_entry)
self.refresh_history()
self.history_text.verticalScrollBar().setValue(
self.history_text.verticalScrollBar().maximum()
)
------------------- Search Functionality -------------------

def toggle_search(self) -> None:
"""Show or hide the search bar."""
is_visible = self.search_bar.isVisible()
self.search_bar.setVisible(not is_visible)
if not is_visible:
self.search_bar.setFocus()
self.search_bar.setText("") # Clear previous search
self.refresh_history()
else:
self.search_bar.clear()

def filter_history(self, text: str) -> None:
"""Filter the history based on the search query."""
self.refresh_history(text)

def refresh_history(self, filter_text: str = "") -> None:
"""Refresh the history text field based on the filter."""
self.history_text.clear()
for entry in self.history_entries:
if filter_text.lower() in entry.lower():
self.history_text.append(entry)

def resizeEvent(self, event) -> None:
"""Handle window resize events to reposition search button and search bar."""
super().resizeEvent(event)
# Reposition search_button
margin_right = 5
margin_top = 5
self.search_button.move(
self.history_text.viewport().width() - self.search_button.width() - margin_right,
margin_top
)
# Reposition search_bar
if self.search_bar.isVisible():
spacing = 5
self.update_search_bar_position()

def update_search_bar_position(self) -> None:
"""Update the position of the search bar relative to the search button."""
spacing = 5
self.search_bar.resize(int(self.history_text.viewport().width() * 0.25), self.search_button.height())
self.search_bar.move(
self.search_button.x() - self.search_bar.width() - spacing,
self.search_button.y()
)

def main() -> None:
"""Run the Calculator application."""
app = QApplication([])
calculator = CalculatorGUI()
calculator.show()
app.exec()

if name == "main":
main()