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:
@@ -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={},
|
||||
|
||||
@@ -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
BIN
dist/DatabaseApp.exe
vendored
Binary file not shown.
116
main.py
116
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(
|
||||
|
||||
Reference in New Issue
Block a user