Initial commit: Kivy database interface application with search, add/update, delete functionality and Windows build support
This commit is contained in:
361
main.py
Normal file
361
main.py
Normal file
@@ -0,0 +1,361 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user