361 lines
14 KiB
Python
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() |