diff --git a/DatabaseApp.spec b/DatabaseApp.spec index 06e1454..b2f3eeb 100644 --- a/DatabaseApp.spec +++ b/DatabaseApp.spec @@ -1,21 +1,31 @@ # -*- mode: python ; coding: utf-8 -*- -from PyInstaller.utils.hooks import collect_all +import os -mysql_datas, mysql_binaries, mysql_hiddenimports = collect_all('mysql.connector') +# Bundle mysql-connector locales to fix "no localization for language 'eng'" error +_mysql_locales = os.path.join( + os.path.dirname(os.path.abspath('.')), + 'db_interface', 'venv', 'Lib', 'site-packages', 'mysql', 'connector', 'locales' +) +# Fallback: resolve relative to spec file location +_spec_dir = os.path.dirname(os.path.abspath(SPEC)) +_mysql_locales = os.path.join(_spec_dir, 'venv', 'Lib', 'site-packages', 'mysql', 'connector', 'locales') a = Analysis( ['main.py'], pathex=[], - binaries=mysql_binaries, - datas=mysql_datas, - hiddenimports=mysql_hiddenimports + [ - 'mysql', 'mysql.connector', 'mysql.connector.locales', - 'mysql.connector.locales.eng', 'mysql.connector.locales.eng.client_error', - 'mysql.connector.plugins', 'mysql.connector.plugins.mysql_native_password', + binaries=[], + datas=[ + (_mysql_locales, os.path.join('mysql', 'connector', 'locales')), + ], + hiddenimports=[ + 'mysql.connector', + 'mysql.connector.locales', + 'mysql.connector.locales.eng', + 'mysql.connector.plugins', + 'mysql.connector.plugins.mysql_native_password', 'mysql.connector.plugins.caching_sha2_password', - 'mysql.connector.aio', 'mysql.connector.aio.plugins', - 'mysql.connector.aio.plugins.mysql_native_password', - 'kivy.core.window.window_sdl2', 'win32timezone', + 'kivy.core.window.window_sdl2', + 'win32timezone', ], hookspath=[], hooksconfig={}, diff --git a/build_windows.py b/build_windows.py index c67c35b..b20fc45 100644 --- a/build_windows.py +++ b/build_windows.py @@ -22,6 +22,13 @@ def build_executable(): else: print("No icon file found (optional)") + # Locate mysql connector locales inside the venv + import site + venv_site = os.path.join('venv', 'Lib', 'site-packages') + mysql_locales = os.path.join(venv_site, 'mysql', 'connector', 'locales') + add_data_sep = ';' if sys.platform == 'win32' else ':' + mysql_locales_arg = f'--add-data={mysql_locales}{add_data_sep}mysql/connector/locales' + # PyInstaller command - simplified to avoid module collection issues cmd = [ 'pyinstaller', @@ -29,11 +36,17 @@ def build_executable(): '--onefile', '--windowed', '--hidden-import=mysql.connector', + '--hidden-import=mysql.connector.locales', + '--hidden-import=mysql.connector.locales.eng', + '--hidden-import=mysql.connector.plugins', + '--hidden-import=mysql.connector.plugins.mysql_native_password', + '--hidden-import=mysql.connector.plugins.caching_sha2_password', '--hidden-import=kivy.core.window.window_sdl2', '--hidden-import=win32timezone', '--exclude-module=_tkinter', '--exclude-module=matplotlib', '--exclude-module=numpy', + mysql_locales_arg, ] + icon_param + ['main.py'] try: diff --git a/dist/DatabaseApp.exe b/dist/DatabaseApp.exe index b7e71ff..2f3b816 100644 Binary files a/dist/DatabaseApp.exe and b/dist/DatabaseApp.exe differ diff --git a/main.py b/main.py index f76dccd..a5e4af0 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,6 @@ from kivy.config import Config Config.set('kivy', 'keyboard_mode', 'system') from kivy.app import App from kivy.uix.boxlayout import BoxLayout -from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.floatlayout import FloatLayout from kivy.uix.gridlayout import GridLayout from kivy.uix.label import Label @@ -26,64 +25,43 @@ class DatabaseApp(App): Window.fullscreen = 'auto' # ------------------------------------------------------------------ - # Responsive sizing: derive all dimensions from the actual screen height - # so the layout fits on any display (800p, 900p, 1080p, etc.) + # Responsive sizing: derive dimensions from the actual screen height # ------------------------------------------------------------------ - wh = Window.height # actual screen height after fullscreen + wh = Window.height - # Numpad occupies 29% of screen height (fixed proportion) - h_numpad_wr = int(wh * 0.29) + # Scale factor (1.0 at 1080p, down to 0.65 on small screens) + s = max(0.65, min(1.0, wh / 1080.0)) - # Main layout outer padding and spacing (scaled) - s = max(0.65, min(1.0, wh / 1080.0)) - pad_v = max(8, int(20 * s)) # top / bottom padding - pad_h = max(8, int(30 * s)) # left / right padding - m_spacing = max(4, int(10 * s)) # gap between content and numpad + # Padding / spacing (all scaled) + pad_v = max(8, int(20 * s)) + pad_h = max(8, int(30 * s)) + m_spacing = max(4, int(10 * s)) + c_spacing = max(4, int(12 * s)) + sp = max(6, int(10 * s)) + upd_pad = max(4, int(8 * s)) + upd_spc = max(4, int(8 * s)) + np_pad_v = max(3, int(6 * s)) + np_spc = max(3, int(6 * s)) - # Space available for the 6 content rows (after numpad + padding + gap) - avail_total = wh - h_numpad_wr - 2 * pad_v - m_spacing - c_spacing = max(4, int(12 * s)) # gap between content rows - avail_items = avail_total - c_spacing * 5 # 6 rows → 5 gaps - - # Distribute height proportionally among rows - # Reference weights: title=50, search=100, mode=38, buttons=65, update=187, status=40 - _w = [50, 100, 38, 65, 187, 40] - _t = sum(_w) - def _h(weight): - return max(24, int(avail_items * weight / _t)) - - h_title = _h(_w[0]) - h_search = _h(_w[1]) - h_row = max(20, h_search // 2) - h_mode = _h(_w[2]) - h_buttons = _h(_w[3]) - h_update = _h(_w[4]) - h_status = _h(_w[5]) - - # Update-frame internal heights - upd_pad = max(4, int(8 * s)) - upd_spc = max(4, int(8 * s)) - h_upd_title = max(20, int(h_update * 0.20)) - h_upd_row = max(20, int(h_update * 0.24)) - h_upd_inputs = h_upd_row * 2 + upd_spc - h_upd_btns = max(28, h_update - h_upd_title - h_upd_inputs - 2*upd_pad - 2*upd_spc) - - # Numpad internal heights - np_pad_v = max(3, int(6 * s)) - np_spc = max(3, int(6 * s)) - h_enter_btn = max(34, int(h_numpad_wr * 0.24)) - h_numpad_gr = h_numpad_wr - h_enter_btn - 2 * np_pad_v - np_spc + # Numpad (fixed height at bottom – 29 % of screen) + h_numpad_wr = int(wh * 0.29) + h_enter_btn = max(34, int(h_numpad_wr * 0.24)) + h_numpad_gr = h_numpad_wr - h_enter_btn - 2 * np_pad_v - np_spc # Font sizes (scaled) - f_title = max(14, int(26 * s)) - f_normal = max(11, int(18 * s)) - f_btn = max(12, int(20 * s)) - f_numpad = max(15, int(26 * s)) - f_enter = max(14, int(24 * s)) - f_mode = max(10, int(16 * s)) - f_override = max(10, int(14 * s)) - f_status = max(12, int(20 * s)) - sp = max(6, int(10 * s)) # generic widget spacing + f_title = max(14, int(26 * s)) + f_normal = max(11, int(18 * s)) + f_btn = max(12, int(20 * s)) + f_numpad = max(15, int(26 * s)) + f_enter = max(14, int(24 * s)) + f_mode = max(10, int(16 * s)) + f_override = max(10, int(14 * s)) + f_status = max(12, int(20 * s)) + + # Proportional size_hint_y weights for the 6 content rows + # title | search | mode | buttons | update-frame | status + _w = [50, 100, 38, 65, 187, 40] + _t = sum(_w) # ------------------------------------------------------------------ # Build UI @@ -98,12 +76,11 @@ class DatabaseApp(App): pos_hint={'x': 0, 'y': 0} ) - # --- Content container (fills remaining space above numpad) --- - content_layout = BoxLayout(orientation='vertical', spacing=c_spacing, size_hint_y=None) - content_layout.bind(minimum_height=content_layout.setter('height')) + # --- Content container: size_hint_y=1 so it fills all space above numpad --- + content_layout = BoxLayout(orientation='vertical', spacing=c_spacing, size_hint_y=1) # Title row: title + Exit button - title_row = BoxLayout(orientation='horizontal', size_hint_y=None, height=h_title, spacing=sp) + title_row = BoxLayout(orientation='horizontal', size_hint_y=_w[0]/_t, spacing=sp) title = Label(text='Database Search & Update', font_size=f_title, bold=True) title_row.add_widget(title) exit_btn = Button( @@ -121,8 +98,8 @@ class DatabaseApp(App): # Search section (ID + Mass) search_layout = GridLayout( - cols=2, size_hint_y=None, height=h_search, - spacing=sp, row_force_default=True, row_default_height=h_row + cols=2, size_hint_y=_w[1]/_t, + spacing=sp ) search_layout.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=f_normal, bold=True)) self.id_input = TextInput( @@ -145,7 +122,7 @@ class DatabaseApp(App): # Mode indicator row self.manual_override = None - mode_row = BoxLayout(orientation='horizontal', size_hint_y=None, height=h_mode, spacing=sp) + mode_row = BoxLayout(orientation='horizontal', size_hint_y=_w[2]/_t, spacing=sp) self.mode_label = Label( text='Article type detected: PRODUCT', size_hint_x=0.75, font_size=f_mode, bold=True, color=(0.4, 0.8, 1, 1) @@ -160,7 +137,7 @@ class DatabaseApp(App): content_layout.add_widget(mode_row) # Action buttons (Add/Update, Reset, Settings) - button_layout = GridLayout(cols=3, size_hint_y=None, height=h_buttons, spacing=sp) + button_layout = GridLayout(cols=3, size_hint_y=_w[3]/_t, spacing=sp) add_update_btn = Button(text='Add/Update', font_size=f_btn, bold=True) add_update_btn.bind(on_press=self.show_update_frame) button_layout.add_widget(add_update_btn) @@ -175,16 +152,15 @@ class DatabaseApp(App): # Update frame self.update_frame = BoxLayout( orientation='vertical', padding=upd_pad, spacing=upd_spc, - size_hint_y=None, height=h_update + size_hint_y=_w[4]/_t ) self.update_frame_label = Label( - text='Update Values', size_hint_y=None, height=h_upd_title, + text='Update Values', size_hint_y=0.20, font_size=f_btn, bold=True ) self.update_frame.add_widget(self.update_frame_label) update_inputs = GridLayout( - cols=2, spacing=sp, size_hint_y=None, height=h_upd_inputs, - row_force_default=True, row_default_height=h_upd_row + cols=2, spacing=sp, size_hint_y=0.52 ) update_inputs.add_widget(Label(text='ID:', size_hint_x=0.25, font_size=f_normal, bold=True)) self.update_id_input = TextInput( @@ -198,7 +174,7 @@ class DatabaseApp(App): self.update_mass_input.bind(focus=self.on_mass_input_focus) update_inputs.add_widget(self.update_mass_input) self.update_frame.add_widget(update_inputs) - update_buttons = GridLayout(cols=2, size_hint_y=None, height=h_upd_btns, spacing=sp) + update_buttons = GridLayout(cols=2, size_hint_y=0.28, spacing=sp) self.update_confirm_btn = Button(text='Confirm Add/Update', disabled=True, font_size=f_btn, bold=True) self.update_confirm_btn.bind(on_press=self.add_update_record) update_buttons.add_widget(self.update_confirm_btn) @@ -212,16 +188,12 @@ class DatabaseApp(App): # Status label self.status_label = Label( - text='Ready', size_hint_y=None, height=h_status, + text='Ready', size_hint_y=_w[5]/_t, color=(0, 0.8, 0, 1), font_size=f_status, bold=True ) content_layout.add_widget(self.status_label) - # Wrap content in an AnchorLayout that fills all space above the numpad - # so the content block is vertically centred regardless of screen size - content_anchor = AnchorLayout(anchor_x='center', anchor_y='center', size_hint_y=1) - content_anchor.add_widget(content_layout) - main_layout.add_widget(content_anchor) + main_layout.add_widget(content_layout) # --- Numeric keypad --- numpad_wrapper = BoxLayout(