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:
parent
8f647a85b9
commit
734600817f
|
@ -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
143
wuttapos/controls/tenkey.py
Normal 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)
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue