Files
db_interface/main.py

361 lines
14 KiB
Python

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.uix.popup import Popup
from kivy.clock import Clock
from kivy.core.window import Window
from database_manager import DatabaseManager
class DatabaseApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.db_manager = DatabaseManager()
def build(self):
# Set window to fullscreen
Window.fullscreen = 'auto'
# Main layout with better spacing for fullscreen
main_layout = BoxLayout(orientation='vertical', padding=40, spacing=20)
# Top spacer for vertical centering
main_layout.add_widget(Label(size_hint_y=0.15))
# Content container - centered
content_layout = BoxLayout(orientation='vertical', spacing=30, size_hint_y=None)
content_layout.bind(minimum_height=content_layout.setter('height'))
# Title
title = Label(text='Database Search & Update', font_size=28, bold=True, size_hint_y=None, height=50)
content_layout.add_widget(title)
# Search section
search_layout = GridLayout(cols=2, size_hint_y=None, height=100, spacing=15, row_force_default=True, row_default_height=45)
search_layout.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=20, bold=True))
self.id_input = TextInput(
multiline=False,
size_hint_x=0.75,
hint_text='Enter ID and press Enter to search (max 20 chars)',
readonly=False,
font_size=20,
padding=[10, 10]
)
self.id_input.bind(on_text_validate=self.search_record)
search_layout.add_widget(self.id_input)
search_layout.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=20, bold=True))
self.mass_input = TextInput(
multiline=False,
size_hint_x=0.75,
hint_text='Mass value (read-only)',
readonly=True,
font_size=20,
padding=[10, 10]
)
search_layout.add_widget(self.mass_input)
content_layout.add_widget(search_layout)
# Button section - larger buttons (3 columns now)
button_layout = GridLayout(cols=3, size_hint_y=None, height=70, spacing=15)
add_update_btn = Button(text='Add/Update', font_size=22, bold=True)
add_update_btn.bind(on_press=self.show_update_frame)
button_layout.add_widget(add_update_btn)
reset_btn = Button(text='Reset Values', font_size=22, bold=True)
reset_btn.bind(on_press=self.reset_values)
button_layout.add_widget(reset_btn)
settings_btn = Button(text='Settings', font_size=22, bold=True)
settings_btn.bind(on_press=self.show_settings)
button_layout.add_widget(settings_btn)
content_layout.add_widget(button_layout)
# Extra spacing between buttons and update frame
content_layout.add_widget(Label(size_hint_y=None, height=40))
# Update frame (initially disabled)
self.update_frame = BoxLayout(orientation='vertical', padding=15, spacing=15, size_hint_y=None, height=200)
self.update_frame_label = Label(text='Update Values', size_hint_y=None, height=40, font_size=22, bold=True)
self.update_frame.add_widget(self.update_frame_label)
update_inputs = GridLayout(cols=2, spacing=15, size_hint_y=None, height=100, row_force_default=True, row_default_height=45)
update_inputs.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=20, bold=True))
self.update_id_input = TextInput(multiline=False, size_hint_x=0.75, readonly=True, font_size=20, padding=[10, 10])
update_inputs.add_widget(self.update_id_input)
update_inputs.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=20, bold=True))
self.update_mass_input = TextInput(multiline=False, size_hint_x=0.75, readonly=True, font_size=20, padding=[10, 10])
update_inputs.add_widget(self.update_mass_input)
self.update_frame.add_widget(update_inputs)
# Add update and delete buttons in same row
update_buttons = GridLayout(cols=2, size_hint_y=None, height=60, spacing=15)
self.update_confirm_btn = Button(text='Confirm Add/Update', disabled=True, font_size=22, bold=True)
self.update_confirm_btn.bind(on_press=self.add_update_record)
update_buttons.add_widget(self.update_confirm_btn)
self.delete_btn = Button(text='Delete', disabled=True, font_size=22, bold=True)
self.delete_btn.bind(on_press=self.delete_record)
update_buttons.add_widget(self.delete_btn)
self.update_frame.add_widget(update_buttons)
content_layout.add_widget(self.update_frame)
# Initially disable update frame
self.set_update_frame_enabled(False)
# Status label - larger and more prominent
self.status_label = Label(
text='Ready',
size_hint_y=None,
height=50,
color=(0, 0.8, 0, 1),
font_size=22,
bold=True
)
content_layout.add_widget(self.status_label)
main_layout.add_widget(content_layout)
# Bottom spacer for vertical centering
main_layout.add_widget(Label(size_hint_y=0.15))
# Removed database contents frame
# Load initial data
Clock.schedule_once(self.refresh_data, 0.1)
return main_layout
def set_update_frame_enabled(self, enabled):
self.update_id_input.readonly = not enabled
self.update_mass_input.readonly = not enabled
self.update_confirm_btn.disabled = not enabled
self.delete_btn.disabled = not enabled
def show_update_frame(self, instance):
# If no value in search, copy from search fields
record_id = self.id_input.text.strip()
mass_text = self.mass_input.text.strip()
self.set_update_frame_enabled(True)
# If mass field is empty, just clear update frame
if not record_id:
self.update_id_input.text = ''
self.update_mass_input.text = ''
return
self.update_id_input.text = record_id
self.update_mass_input.text = mass_text
def search_record(self, instance):
record_id = self.id_input.text.strip()
if not record_id:
self.show_status("Please enter an ID to search", error=True)
return
if len(record_id) > 20:
self.show_status("ID must be 20 characters or less", error=True)
return
# Show searching status
self.show_status("Searching...", error=False)
try:
record = self.db_manager.search_by_id(record_id)
if record:
self.mass_input.text = str(record[1]) # Set the mass field
self.show_status(f"Found: {record[0]} = {record[1]}")
self.highlight_record(record_id)
else:
self.show_status(f"ID '{record_id}' not found in database", error=True)
self.mass_input.text = ""
except Exception as e:
self.show_status(f"Search error: {str(e)}", error=True)
def add_update_record(self, instance):
"""Add or update a record from the update frame."""
record_id = self.update_id_input.text.strip()
mass_text = self.update_mass_input.text.strip()
if not record_id or not mass_text:
self.show_status("Please enter both ID and mass in update frame", error=True)
return
if len(record_id) > 20:
self.show_status("ID must be 20 characters or less", error=True)
return
try:
mass = float(mass_text)
except ValueError:
self.show_status("Mass must be a valid number", error=True)
return
success = self.db_manager.add_or_update_record(record_id, mass)
if success:
existing = self.db_manager.search_by_id(record_id)
if existing:
self.show_status(f"Successfully added/updated: {record_id} = {mass}")
self.refresh_data(None)
# Clear update frame after successful operation
self.update_id_input.text = ""
self.update_mass_input.text = ""
self.set_update_frame_enabled(False)
else:
self.show_status("Operation completed but record not found", error=True)
else:
self.show_status("Failed to add/update record", error=True)
def delete_record(self, instance):
"""Delete a record using the update frame fields."""
record_id = self.update_id_input.text.strip()
if not record_id:
self.show_status("Please enter an ID in the update fields to delete", error=True)
return
# Confirm deletion
self.show_confirmation_popup(
f"Are you sure you want to delete ID '{record_id}'?",
lambda: self.confirm_delete(record_id)
)
def confirm_delete(self, record_id):
"""Confirm and execute deletion."""
success = self.db_manager.delete_record(record_id)
if success:
self.show_status(f"Successfully deleted: {record_id}")
self.refresh_data(None)
self.clear_fields()
# Clear update frame fields
self.update_id_input.text = ""
self.update_mass_input.text = ""
self.set_update_frame_enabled(False)
else:
self.show_status(f"Failed to delete ID '{record_id}' (not found)", error=True)
def clear_fields(self):
"""Clear the ID and mass fields."""
self.id_input.text = ""
self.mass_input.text = ""
def reset_values(self, instance):
"""Reset/clear the first ID and mass fields and set focus on ID field."""
self.id_input.text = ""
self.mass_input.text = ""
self.id_input.focus = True
self.show_status("Fields cleared", error=False)
def refresh_data(self, instance):
"""Refresh the data display."""
try:
records = self.db_manager.read_all_data()
count = len(records)
self.show_status(f"Data refreshed - {count} records found")
except Exception as e:
self.show_status(f"Error refreshing data: {str(e)}", error=True)
def highlight_record(self, record_id):
"""Highlight a specific record in the display."""
# Since we removed the data display, just show a status message
pass
def show_status(self, message, error=False):
"""Show status message."""
self.status_label.text = message
if error:
self.status_label.color = (1, 0.2, 0.2, 1) # Red for errors
else:
self.status_label.color = (0, 0.8, 0, 1) # Green for success
# Clear status after 5 seconds
Clock.schedule_once(lambda dt: self.clear_status(), 5)
def clear_status(self):
"""Clear the status message."""
self.status_label.text = "Ready"
self.status_label.color = (0, 0.8, 0, 1)
def show_settings(self, instance):
"""Show settings popup for database configuration."""
content = BoxLayout(orientation='vertical', spacing=15, padding=20)
# Title
title_label = Label(text='Database Server Settings', font_size=24, bold=True, size_hint_y=None, height=40)
content.add_widget(title_label)
# IP address input
ip_layout = BoxLayout(orientation='horizontal', size_hint_y=None, height=60, spacing=10)
ip_layout.add_widget(Label(text='Server IP Address:', font_size=20, bold=True, size_hint_x=0.4))
ip_input = TextInput(
text=self.db_manager.host,
multiline=False,
font_size=20,
size_hint_x=0.6,
padding=[10, 10]
)
ip_layout.add_widget(ip_input)
content.add_widget(ip_layout)
# Info label
info_label = Label(
text='Enter the IP address or hostname of the database server.\nOther connection settings remain unchanged.',
font_size=16,
size_hint_y=None,
height=60
)
content.add_widget(info_label)
# Buttons
buttons = BoxLayout(size_hint_y=None, height=60, spacing=15)
save_btn = Button(text='Save', font_size=20, bold=True)
cancel_btn = Button(text='Cancel', font_size=20, bold=True)
buttons.add_widget(save_btn)
buttons.add_widget(cancel_btn)
content.add_widget(buttons)
popup = Popup(
title='Settings',
content=content,
size_hint=(0.6, 0.5)
)
def save_settings():
new_host = ip_input.text.strip()
if new_host:
self.db_manager.host = new_host
# Reconnect with new settings
if self.db_manager.connection:
try:
self.db_manager.connection.close()
except:
pass
self.db_manager.connection = None
self.show_status(f"Database server updated to: {new_host}")
popup.dismiss()
else:
self.show_status("Please enter a valid IP address", error=True)
save_btn.bind(on_press=lambda x: save_settings())
cancel_btn.bind(on_press=popup.dismiss)
popup.open()
def show_confirmation_popup(self, message, confirm_callback):
"""Show a confirmation popup."""
content = BoxLayout(orientation='vertical', spacing=10, padding=10)
label = Label(text=message, text_size=(300, None), halign='center')
content.add_widget(label)
buttons = BoxLayout(size_hint_y=None, height=50, spacing=10)
yes_btn = Button(text='Yes')
no_btn = Button(text='No')
buttons.add_widget(yes_btn)
buttons.add_widget(no_btn)
content.add_widget(buttons)
popup = Popup(
title='Confirm Action',
content=content,
size_hint=(0.8, 0.4)
)
yes_btn.bind(on_press=lambda x: (confirm_callback(), popup.dismiss()))
no_btn.bind(on_press=popup.dismiss)
popup.open()
if __name__ == '__main__':
DatabaseApp().run()