387 lines
12 KiB
Python
387 lines
12 KiB
Python
"""
|
||
EU-Utility - Calculator Plugin
|
||
|
||
Standard calculator with Windows-style layout.
|
||
"""
|
||
|
||
from PyQt6.QtWidgets import (
|
||
QWidget, QVBoxLayout, QHBoxLayout,
|
||
QLineEdit, QPushButton, QLabel, QGridLayout
|
||
)
|
||
from PyQt6.QtCore import Qt
|
||
|
||
from plugins.base_plugin import BasePlugin
|
||
|
||
|
||
class CalculatorPlugin(BasePlugin):
|
||
"""Standard calculator with Windows-style layout."""
|
||
|
||
name = "Calculator"
|
||
version = "1.1.0"
|
||
author = "ImpulsiveFPS"
|
||
description = "Standard calculator"
|
||
hotkey = "ctrl+shift+c"
|
||
|
||
def initialize(self):
|
||
"""Setup calculator."""
|
||
self.current_value = "0"
|
||
self.stored_value = None
|
||
self.pending_op = None
|
||
self.memory = 0
|
||
self.start_new = True
|
||
|
||
def get_ui(self):
|
||
"""Create calculator UI with Windows layout."""
|
||
widget = QWidget()
|
||
layout = QVBoxLayout(widget)
|
||
layout.setSpacing(2)
|
||
|
||
# Title
|
||
title = QLabel("🧮 Calculator")
|
||
title.setStyleSheet("color: #4a9eff; font-size: 16px; font-weight: bold;")
|
||
layout.addWidget(title)
|
||
|
||
# Display
|
||
self.display = QLineEdit("0")
|
||
self.display.setAlignment(Qt.AlignmentFlag.AlignRight)
|
||
self.display.setStyleSheet("""
|
||
QLineEdit {
|
||
background-color: #1a1a1a;
|
||
color: white;
|
||
font-size: 32px;
|
||
font-family: 'Segoe UI', Arial;
|
||
padding: 15px;
|
||
border: none;
|
||
border-radius: 4px;
|
||
}
|
||
""")
|
||
self.display.setReadOnly(True)
|
||
layout.addWidget(self.display)
|
||
|
||
# Button grid - Windows Calculator Layout
|
||
grid = QGridLayout()
|
||
grid.setSpacing(2)
|
||
|
||
# Row 1: Memory buttons
|
||
mem_buttons = ['MC', 'MR', 'M+', 'M-', 'MS', 'M~']
|
||
for i, btn_text in enumerate(mem_buttons):
|
||
btn = self._create_button(btn_text, "#3a3a3a")
|
||
btn.clicked.connect(lambda checked, t=btn_text: self._on_memory(t))
|
||
grid.addWidget(btn, 0, i)
|
||
|
||
# Row 2: %, CE, C, ⌫
|
||
row2 = ['%', 'CE', 'C', '⌫']
|
||
for i, btn_text in enumerate(row2):
|
||
btn = self._create_button(btn_text, "#3a3a3a")
|
||
btn.clicked.connect(lambda checked, t=btn_text: self._on_special(t))
|
||
grid.addWidget(btn, 1, i)
|
||
|
||
# Row 3: ¹/ₓ, x², ²√x, ÷
|
||
row3 = [('¹/ₓ', '1/x'), ('x²', 'sq'), ('²√x', 'sqrt'), '÷']
|
||
for i, item in enumerate(row3):
|
||
if isinstance(item, tuple):
|
||
text, op = item
|
||
else:
|
||
text = op = item
|
||
btn = self._create_button(text, "#3a3a3a")
|
||
btn.clicked.connect(lambda checked, o=op: self._on_operator(o))
|
||
grid.addWidget(btn, 2, i)
|
||
|
||
# Row 4: 7, 8, 9, ×
|
||
row4 = ['7', '8', '9', '×']
|
||
for i, btn_text in enumerate(row4):
|
||
btn = self._create_button(btn_text, "#2a2a2a", is_number=btn_text not in ['×'])
|
||
if btn_text == '×':
|
||
btn.clicked.connect(lambda checked: self._on_operator('*'))
|
||
else:
|
||
btn.clicked.connect(lambda checked, t=btn_text: self._on_number(t))
|
||
grid.addWidget(btn, 3, i)
|
||
|
||
# Row 5: 4, 5, 6, -
|
||
row5 = ['4', '5', '6', '-']
|
||
for i, btn_text in enumerate(row5):
|
||
btn = self._create_button(btn_text, "#2a2a2a", is_number=btn_text not in ['-'])
|
||
if btn_text == '-':
|
||
btn.clicked.connect(lambda checked: self._on_operator('-'))
|
||
else:
|
||
btn.clicked.connect(lambda checked, t=btn_text: self._on_number(t))
|
||
grid.addWidget(btn, 4, i)
|
||
|
||
# Row 6: 1, 2, 3, +
|
||
row6 = ['1', '2', '3', '+']
|
||
for i, btn_text in enumerate(row6):
|
||
btn = self._create_button(btn_text, "#2a2a2a", is_number=btn_text not in ['+'])
|
||
if btn_text == '+':
|
||
btn.clicked.connect(lambda checked: self._on_operator('+'))
|
||
else:
|
||
btn.clicked.connect(lambda checked, t=btn_text: self._on_number(t))
|
||
grid.addWidget(btn, 5, i)
|
||
|
||
# Row 7: +/-, 0, ., =
|
||
row7 = [('±', '+/-'), '0', '.', '=']
|
||
for i, item in enumerate(row7):
|
||
if isinstance(item, tuple):
|
||
text, val = item
|
||
else:
|
||
text = val = item
|
||
|
||
if val == '=':
|
||
btn = self._create_button(text, "#0078d4", text_color="white") # Blue equals
|
||
else:
|
||
btn = self._create_button(text, "#2a2a2a", is_number=True)
|
||
|
||
if val == '+/-':
|
||
btn.clicked.connect(self._on_negate)
|
||
elif val == '.':
|
||
btn.clicked.connect(self._on_decimal)
|
||
elif val == '=':
|
||
btn.clicked.connect(self._on_equals)
|
||
else:
|
||
btn.clicked.connect(lambda checked, t=val: self._on_number(t))
|
||
|
||
grid.addWidget(btn, 6, i)
|
||
|
||
# Set column stretch
|
||
for i in range(4):
|
||
grid.setColumnStretch(i, 1)
|
||
|
||
# Set row stretch
|
||
for i in range(7):
|
||
grid.setRowStretch(i, 1)
|
||
|
||
layout.addLayout(grid)
|
||
layout.addStretch()
|
||
|
||
return widget
|
||
|
||
def _create_button(self, text, bg_color, is_number=False, text_color="#ffffff"):
|
||
"""Create a calculator button."""
|
||
btn = QPushButton(text)
|
||
btn.setMinimumSize(60, 45)
|
||
|
||
if is_number:
|
||
font_size = "18px"
|
||
font_weight = "normal"
|
||
else:
|
||
font_size = "14px"
|
||
font_weight = "normal"
|
||
|
||
btn.setStyleSheet(f"""
|
||
QPushButton {{
|
||
background-color: {bg_color};
|
||
color: {text_color};
|
||
font-size: {font_size};
|
||
font-weight: {font_weight};
|
||
border: none;
|
||
border-radius: 4px;
|
||
}}
|
||
QPushButton:hover {{
|
||
background-color: {self._lighten(bg_color)};
|
||
}}
|
||
QPushButton:pressed {{
|
||
background-color: {self._darken(bg_color)};
|
||
}}
|
||
""")
|
||
|
||
return btn
|
||
|
||
def _lighten(self, color):
|
||
"""Lighten a hex color slightly."""
|
||
# Simple approximation - increase each component
|
||
if color == "#2a2a2a":
|
||
return "#3a3a3a"
|
||
elif color == "#3a3a3a":
|
||
return "#4a4a4a"
|
||
elif color == "#0078d4":
|
||
return "#1084e0"
|
||
return color
|
||
|
||
def _darken(self, color):
|
||
"""Darken a hex color slightly."""
|
||
if color == "#2a2a2a":
|
||
return "#1a1a1a"
|
||
elif color == "#3a3a3a":
|
||
return "#2a2a2a"
|
||
elif color == "#0078d4":
|
||
return "#006cbd"
|
||
return color
|
||
|
||
def _on_number(self, num):
|
||
"""Handle number button press."""
|
||
if self.start_new:
|
||
self.current_value = num
|
||
self.start_new = False
|
||
else:
|
||
if self.current_value == "0":
|
||
self.current_value = num
|
||
else:
|
||
self.current_value += num
|
||
self._update_display()
|
||
|
||
def _on_decimal(self):
|
||
"""Handle decimal point."""
|
||
if self.start_new:
|
||
self.current_value = "0."
|
||
self.start_new = False
|
||
elif "." not in self.current_value:
|
||
self.current_value += "."
|
||
self._update_display()
|
||
|
||
def _on_operator(self, op):
|
||
"""Handle operator button."""
|
||
try:
|
||
current = float(self.current_value)
|
||
|
||
if op == "1/x":
|
||
result = 1 / current
|
||
self.current_value = self._format_result(result)
|
||
self.start_new = True
|
||
elif op == "sq":
|
||
result = current ** 2
|
||
self.current_value = self._format_result(result)
|
||
self.start_new = True
|
||
elif op == "sqrt":
|
||
import math
|
||
result = math.sqrt(current)
|
||
self.current_value = self._format_result(result)
|
||
self.start_new = True
|
||
else:
|
||
# Binary operators
|
||
if self.pending_op and not self.start_new:
|
||
self._calculate()
|
||
|
||
self.stored_value = float(self.current_value)
|
||
self.pending_op = op
|
||
self.start_new = True
|
||
|
||
self._update_display()
|
||
except Exception:
|
||
self.current_value = "Error"
|
||
self._update_display()
|
||
self.start_new = True
|
||
|
||
def _on_special(self, op):
|
||
"""Handle special buttons (%, CE, C, backspace)."""
|
||
if op == 'C':
|
||
# Clear all
|
||
self.current_value = "0"
|
||
self.stored_value = None
|
||
self.pending_op = None
|
||
self.start_new = True
|
||
elif op == 'CE':
|
||
# Clear entry
|
||
self.current_value = "0"
|
||
self.start_new = True
|
||
elif op == '⌫':
|
||
# Backspace
|
||
if len(self.current_value) > 1:
|
||
self.current_value = self.current_value[:-1]
|
||
else:
|
||
self.current_value = "0"
|
||
elif op == '%':
|
||
# Percent
|
||
try:
|
||
result = float(self.current_value) / 100
|
||
self.current_value = self._format_result(result)
|
||
self.start_new = True
|
||
except:
|
||
self.current_value = "Error"
|
||
self.start_new = True
|
||
|
||
self._update_display()
|
||
|
||
def _on_memory(self, op):
|
||
"""Handle memory operations."""
|
||
try:
|
||
current = float(self.current_value)
|
||
|
||
if op == 'MC':
|
||
self.memory = 0
|
||
elif op == 'MR':
|
||
self.current_value = self._format_result(self.memory)
|
||
self.start_new = True
|
||
elif op == 'M+':
|
||
self.memory += current
|
||
elif op == 'M-':
|
||
self.memory -= current
|
||
elif op == 'MS':
|
||
self.memory = current
|
||
elif op == 'M~':
|
||
# Memory clear (same as MC)
|
||
self.memory = 0
|
||
|
||
self._update_display()
|
||
except:
|
||
pass
|
||
|
||
def _on_negate(self):
|
||
"""Toggle sign."""
|
||
try:
|
||
current = float(self.current_value)
|
||
result = -current
|
||
self.current_value = self._format_result(result)
|
||
self._update_display()
|
||
except:
|
||
pass
|
||
|
||
def _on_equals(self):
|
||
"""Calculate result."""
|
||
self._calculate()
|
||
self.pending_op = None
|
||
self.stored_value = None
|
||
self.start_new = True
|
||
|
||
def _calculate(self):
|
||
"""Perform pending calculation."""
|
||
if self.pending_op and self.stored_value is not None:
|
||
try:
|
||
current = float(self.current_value)
|
||
|
||
if self.pending_op == '+':
|
||
result = self.stored_value + current
|
||
elif self.pending_op == '-':
|
||
result = self.stored_value - current
|
||
elif self.pending_op == '*':
|
||
result = self.stored_value * current
|
||
elif self.pending_op == '÷':
|
||
if current != 0:
|
||
result = self.stored_value / current
|
||
else:
|
||
result = "Error"
|
||
else:
|
||
return
|
||
|
||
self.current_value = self._format_result(result)
|
||
self._update_display()
|
||
except:
|
||
self.current_value = "Error"
|
||
self._update_display()
|
||
|
||
def _format_result(self, result):
|
||
"""Format calculation result."""
|
||
if isinstance(result, str):
|
||
return result
|
||
|
||
# Check if it's essentially an integer
|
||
if result == int(result):
|
||
return str(int(result))
|
||
|
||
# Format with reasonable precision
|
||
formatted = f"{result:.10f}"
|
||
# Remove trailing zeros
|
||
formatted = formatted.rstrip('0').rstrip('.')
|
||
|
||
# Limit length
|
||
if len(formatted) > 12:
|
||
formatted = f"{result:.6e}"
|
||
|
||
return formatted
|
||
|
||
def _update_display(self):
|
||
"""Update the display."""
|
||
self.display.setText(self.current_value)
|
||
|
||
def on_hotkey(self):
|
||
"""Focus calculator when hotkey pressed."""
|
||
pass
|