fix: dynamic height layout + bundle mysql locales to fix 'no localization for eng' error\n\n- Replace all fixed pixel heights in content_layout with size_hint_y proportions\n so the 6 rows fill available space on any screen (800p, 1080p, etc.)\n- Remove AnchorLayout wrapper that caused dead space above content\n- Bundle mysql/connector/locales in PyInstaller build (spec + build_windows.py)\n- Add mysql.connector.plugins hidden imports to spec and build script"

This commit is contained in:
2026-04-07 16:52:10 +03:00
parent b51e8bcc2a
commit 0f7e157406
4 changed files with 78 additions and 83 deletions

View File

@@ -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={},

View File

@@ -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:

BIN
dist/DatabaseApp.exe vendored

Binary file not shown.

88
main.py
View File

@@ -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,51 +25,26 @@ 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)
# Main layout outer padding and spacing (scaled)
# Scale factor (1.0 at 1080p, down to 0.65 on small screens)
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
# 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
# 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))
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))
# 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
@@ -83,7 +57,11 @@ class DatabaseApp(App):
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
# 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(