Split the tenkey menu into separate control

hoping to re-use on the login screen, but also makes it easier to
override if needed
This commit is contained in:
Lance Edgar 2023-10-05 15:12:06 -05:00
parent 8f647a85b9
commit 734600817f
3 changed files with 262 additions and 168 deletions

View file

@ -26,6 +26,8 @@ WuttaPOS - custom controls (base class)
import flet as ft import flet as ft
from wuttapos.util import make_button
class WuttaControl(ft.UserControl): class WuttaControl(ft.UserControl):
@ -39,6 +41,9 @@ class WuttaControl(ft.UserControl):
def informed_refresh(self, **kwargs): def informed_refresh(self, **kwargs):
pass pass
def make_button(self, *args, **kwargs):
return make_button(*args, **kwargs)
def reset(self, e=None): def reset(self, e=None):
if self.on_reset: if self.on_reset:
self.on_reset(e=e) self.on_reset(e=e)

143
wuttapos/controls/tenkey.py Normal file
View file

@ -0,0 +1,143 @@
# -*- coding: utf-8; -*-
################################################################################
#
# WuttaPOS -- Pythonic Point of Sale System
# Copyright © 2023 Lance Edgar
#
# This file is part of WuttaPOS.
#
# WuttaPOS is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# WuttaPOS is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# WuttaPOS. If not, see <http://www.gnu.org/licenses/>.
#
################################################################################
"""
WuttaPOS - ten-key control
"""
import flet as ft
from .base import WuttaControl
class WuttaTenkeyMenu(WuttaControl):
default_font_size = 40
default_button_size = 100
def __init__(self, *args, **kwargs):
self.on_char = kwargs.pop('on_char', None)
self.on_enter = kwargs.pop('on_enter', None)
self.on_up_click = kwargs.pop('on_up_click', None)
self.on_up_longpress = kwargs.pop('on_up_longpress', None)
self.on_down_click = kwargs.pop('on_down_click', None)
self.on_down_longpress = kwargs.pop('on_down_longpress', None)
super().__init__(*args, **kwargs)
def build(self):
return ft.Container(
content=ft.Column(
[
ft.Row(
[
self.make_tenkey_button("1"),
self.make_tenkey_button("2"),
self.make_tenkey_button("3"),
self.make_tenkey_button("@"),
],
spacing=0,
),
ft.Row(
[
self.make_tenkey_button("4"),
self.make_tenkey_button("5"),
self.make_tenkey_button("6"),
self.make_tenkey_button("", on_long_press=self.up_long_press),
],
spacing=0,
),
ft.Row(
[
self.make_tenkey_button("7"),
self.make_tenkey_button("8"),
self.make_tenkey_button("9"),
self.make_tenkey_button("", on_long_press=self.down_long_press),
],
spacing=0,
),
ft.Row(
[
self.make_tenkey_button("0"),
# self.make_tenkey_button("00"),
self.make_tenkey_button("."),
# TODO: should just specify 2x multiplier instead
self.make_tenkey_button("ENTER", width=self.default_button_size * 2),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
spacing=0,
),
],
spacing=0,
),
)
def make_tenkey_button(
self,
text,
font_size=None,
bgcolor='green',
height=None,
width=None,
on_click=None,
on_long_press=None,
):
if not font_size:
font_size = self.default_font_size
if not height:
height = self.default_button_size
if not width:
width = self.default_button_size
if not on_click:
on_click = self.tenkey_click
return self.make_button(text, font_size=font_size, bgcolor=bgcolor,
height=height, width=width,
on_click=on_click,
on_long_press=on_long_press)
def tenkey_click(self, e):
value = e.control.content.value
if value == 'ENTER':
if self.on_enter:
self.on_enter(e)
elif value == '': # UP
if self.on_up_click:
self.on_up_click(e)
elif value == '': # DOWN
if self.on_down_click:
self.on_down_click(e)
else: # normal char key
if self.on_char:
self.on_char(value)
def up_long_press(self, e):
if self.on_up_longpress:
self.on_up_longpress(e)
def down_long_press(self, e):
if self.on_down_longpress:
self.on_down_longpress(e)

View file

@ -34,6 +34,7 @@ from .base import WuttaView
from wuttapos.controls.custlookup import WuttaCustomerLookup from wuttapos.controls.custlookup import WuttaCustomerLookup
from wuttapos.controls.itemlookup import WuttaProductLookup from wuttapos.controls.itemlookup import WuttaProductLookup
from wuttapos.controls.txnitem import WuttaTxnItem from wuttapos.controls.txnitem import WuttaTxnItem
from wuttapos.controls.tenkey import WuttaTenkeyMenu
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -479,6 +480,88 @@ class POSView(WuttaView):
# no value provided, so do lookup # no value provided, so do lookup
self.customer_lookup() self.customer_lookup()
def tenkey_char(self, key):
if key == '@':
quantity = self.main_input.value
valid = False
if self.set_quantity.data is not None:
quantity = self.set_quantity.data
self.page.snack_bar = ft.SnackBar(ft.Text(f"QUANTITY ALREADY SET: {quantity}",
color='black',
size=20,
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
else:
try:
quantity = decimal.Decimal(quantity)
valid = True
except decimal.InvalidOperation:
pass
if valid and quantity:
self.set_quantity.data = quantity
self.set_quantity.value = self.app.render_quantity(quantity) + " @ "
self.main_input.value = ""
self.main_input.focus()
else:
self.page.snack_bar = ft.SnackBar(ft.Text(f"INVALID @ QUANTITY: {quantity}",
color='black',
size=20,
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.page.update()
else: # normal char
self.main_input.value = f"{self.main_input.value or ''}{key}"
self.main_input.focus()
self.page.update()
def tenkey_enter(self, e):
self.main_submit()
def tenkey_up_click(self, e):
# select previous item, if selection in progress
if self.selected_item:
i = self.items.controls.index(self.selected_item)
if i > 0:
self.items.scroll_to(delta=-50, duration=100)
self.select_txn_item(self.items.controls[i - 1])
return
self.items.scroll_to(delta=-50, duration=100)
self.page.update()
def tenkey_up_longpress(self, e):
self.items.scroll_to(delta=-500, duration=100)
self.page.update()
def tenkey_down_click(self, e):
# select next item, if selection in progress
if self.selected_item:
i = self.items.controls.index(self.selected_item)
if (i + 1) < len(self.items.controls):
self.items.scroll_to(delta=50, duration=100)
self.select_txn_item(self.items.controls[i + 1])
return
self.items.scroll_to(delta=50, duration=100)
self.page.update()
def tenkey_down_longpress(self, e):
self.items.scroll_to(delta=500, duration=100)
self.page.update()
def build_controls(self): def build_controls(self):
session = self.app.make_session() session = self.app.make_session()
@ -512,176 +595,35 @@ class POSView(WuttaView):
expand=1, expand=1,
) )
def tenkey_click(e): def backspace_click(e):
value = e.control.content.value if self.main_input.value:
self.main_input.value = self.main_input.value[:-1]
if value == 'ENTER': self.main_input.focus()
self.main_submit()
elif value == '': # backspace
if self.main_input.value:
self.main_input.value = self.main_input.value[:-1]
self.main_input.focus()
self.page.update()
elif value == 'CE': # clear entry
if self.main_input.value:
self.main_input.value = ""
elif self.set_quantity.data is not None:
self.set_quantity.data = None
self.set_quantity.value = None
elif self.selected_item:
self.selected_item.bgcolor = 'white'
self.selected_item = None
self.main_input.focus()
self.page.update()
elif value == '@':
quantity = self.main_input.value
valid = False
if self.set_quantity.data is not None:
quantity = self.set_quantity.data
self.page.snack_bar = ft.SnackBar(ft.Text(f"QUANTITY ALREADY SET: {quantity}",
color='black',
size=20,
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
else:
try:
quantity = decimal.Decimal(quantity)
valid = True
except decimal.InvalidOperation:
pass
if valid and quantity:
self.set_quantity.data = quantity
self.set_quantity.value = self.app.render_quantity(quantity) + " @ "
self.main_input.value = ""
self.main_input.focus()
else:
self.page.snack_bar = ft.SnackBar(ft.Text(f"INVALID @ QUANTITY: {quantity}",
color='black',
size=20,
weight=ft.FontWeight.BOLD),
bgcolor='yellow',
duration=1500)
self.page.snack_bar.open = True
self.page.update()
elif value == '': # UP
# select previous item, if selection in progress
if self.selected_item:
i = self.items.controls.index(self.selected_item)
if i > 0:
self.items.scroll_to(delta=-50, duration=100)
self.select_txn_item(self.items.controls[i - 1])
return
self.items.scroll_to(delta=-50, duration=100)
self.page.update()
elif value == '':
# select next item, if selection in progress
if self.selected_item:
i = self.items.controls.index(self.selected_item)
if (i + 1) < len(self.items.controls):
self.items.scroll_to(delta=50, duration=100)
self.select_txn_item(self.items.controls[i + 1])
return
self.items.scroll_to(delta=50, duration=100)
self.page.update()
else:
self.main_input.value = f"{self.main_input.value or ''}{value}"
self.main_input.focus()
self.page.update()
def up_long_press(e):
self.items.scroll_to(delta=-500, duration=100)
self.page.update() self.page.update()
def down_long_press(e): def clear_entry_click(e):
self.items.scroll_to(delta=500, duration=100) if self.main_input.value:
self.main_input.value = ""
elif self.set_quantity.data is not None:
self.set_quantity.data = None
self.set_quantity.value = None
elif self.selected_item:
self.selected_item.bgcolor = 'white'
self.selected_item = None
self.main_input.focus()
self.page.update() self.page.update()
tenkey_button_size = self.default_button_size self.tenkey_menu = WuttaTenkeyMenu(self.config,
tenkey_font_size = self.default_font_size on_char=self.tenkey_char,
on_enter=self.tenkey_enter,
on_up_click=self.tenkey_up_click,
on_up_longpress=self.tenkey_up_longpress,
on_down_click=self.tenkey_down_click,
on_down_longpress=self.tenkey_down_longpress)
def tenkey_button(text, meta_button_height = self.default_button_size
bgcolor='green',
height=tenkey_button_size,
width=tenkey_button_size,
on_click=tenkey_click,
on_long_press=None,
):
return ft.Container(content=ft.Text(text, size=tenkey_font_size,
weight=ft.FontWeight.BOLD),
height=height,
width=width,
on_click=on_click,
on_long_press=on_long_press,
alignment=ft.alignment.center,
border=ft.border.all(1, 'black'),
border_radius=ft.border_radius.all(5),
bgcolor=bgcolor)
self.tenkey_menu = ft.Container(
content=ft.Column(
[
ft.Row(
[
tenkey_button("1"),
tenkey_button("2"),
tenkey_button("3"),
tenkey_button("@"),
],
spacing=0,
),
ft.Row(
[
tenkey_button("4"),
tenkey_button("5"),
tenkey_button("6"),
tenkey_button("", on_long_press=up_long_press),
],
spacing=0,
),
ft.Row(
[
tenkey_button("7"),
tenkey_button("8"),
tenkey_button("9"),
tenkey_button("", on_long_press=down_long_press),
],
spacing=0,
),
ft.Row(
[
tenkey_button("0"),
# tenkey_button("00"),
tenkey_button("."),
tenkey_button("ENTER", width=tenkey_button_size * 2),
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
spacing=0,
),
],
spacing=0,
),
expand=0)
meta_button_height = tenkey_button_size
meta_button_width = meta_button_height * 2 meta_button_width = meta_button_height * 2
meta_font_size = tenkey_font_size meta_font_size = self.default_font_size
def meta_button(text, on_click=None, bgcolor='blue', data=None, def meta_button(text, on_click=None, bgcolor='blue', data=None,
font_size=None): font_size=None):
@ -733,9 +675,9 @@ class POSView(WuttaView):
), ),
expand=0) expand=0)
context_button_height = tenkey_button_size context_button_height = self.default_button_size
context_button_width = context_button_height * 2 context_button_width = context_button_height * 2
context_font_size = tenkey_font_size context_font_size = self.default_font_size
def context_button(text, on_click=None, data=None): def context_button(text, on_click=None, data=None):
return ft.Container(content=ft.Text(text, size=context_font_size, return ft.Container(content=ft.Text(text, size=context_font_size,
@ -776,8 +718,12 @@ class POSView(WuttaView):
[ [
self.set_quantity, self.set_quantity,
self.main_input, self.main_input,
tenkey_button("", height=70, width=70), self.make_button("", font_size=40, bgcolor='green',
tenkey_button("CE", height=70, width=70), height=70, width=70,
on_click=backspace_click),
self.make_button("CE", font_size=40, bgcolor='green',
height=70, width=70,
on_click=clear_entry_click),
], ],
alignment=ft.MainAxisAlignment.CENTER, alignment=ft.MainAxisAlignment.CENTER,
expand=True, expand=True,