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 serach bar should open to the right of the button with the right border of the results field as it boundry 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()