diff --git a/wuttapos/controls/base.py b/wuttapos/controls/base.py index b7d7e0e..a89d762 100644 --- a/wuttapos/controls/base.py +++ b/wuttapos/controls/base.py @@ -26,6 +26,8 @@ WuttaPOS - custom controls (base class) import flet as ft +from wuttapos.util import make_button + class WuttaControl(ft.UserControl): @@ -39,6 +41,9 @@ class WuttaControl(ft.UserControl): def informed_refresh(self, **kwargs): pass + def make_button(self, *args, **kwargs): + return make_button(*args, **kwargs) + def reset(self, e=None): if self.on_reset: self.on_reset(e=e) diff --git a/wuttapos/controls/tenkey.py b/wuttapos/controls/tenkey.py new file mode 100644 index 0000000..cceaae1 --- /dev/null +++ b/wuttapos/controls/tenkey.py @@ -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 . +# +################################################################################ +""" +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) diff --git a/wuttapos/views/pos.py b/wuttapos/views/pos.py index 99f2666..e4d444c 100644 --- a/wuttapos/views/pos.py +++ b/wuttapos/views/pos.py @@ -34,6 +34,7 @@ from .base import WuttaView from wuttapos.controls.custlookup import WuttaCustomerLookup from wuttapos.controls.itemlookup import WuttaProductLookup from wuttapos.controls.txnitem import WuttaTxnItem +from wuttapos.controls.tenkey import WuttaTenkeyMenu log = logging.getLogger(__name__) @@ -479,6 +480,88 @@ class POSView(WuttaView): # no value provided, so do 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): session = self.app.make_session() @@ -512,176 +595,35 @@ class POSView(WuttaView): expand=1, ) - def tenkey_click(e): - value = e.control.content.value - - if value == 'ENTER': - 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) + def backspace_click(e): + if self.main_input.value: + self.main_input.value = self.main_input.value[:-1] + self.main_input.focus() self.page.update() - def down_long_press(e): - self.items.scroll_to(delta=500, duration=100) + def clear_entry_click(e): + 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() - tenkey_button_size = self.default_button_size - tenkey_font_size = self.default_font_size + self.tenkey_menu = WuttaTenkeyMenu(self.config, + 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, - 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_height = self.default_button_size 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, font_size=None): @@ -733,9 +675,9 @@ class POSView(WuttaView): ), expand=0) - context_button_height = tenkey_button_size + context_button_height = self.default_button_size 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): return ft.Container(content=ft.Text(text, size=context_font_size, @@ -776,8 +718,12 @@ class POSView(WuttaView): [ self.set_quantity, self.main_input, - tenkey_button("⌫", height=70, width=70), - tenkey_button("CE", height=70, width=70), + self.make_button("⌫", font_size=40, bgcolor='green', + 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, expand=True,