Commit 23ebb762 authored by Robin Trioux's avatar Robin Trioux
Browse files

QAtoms in Trees paradigm

Now the client give atoms to Managers.
So Managers can be called from anywhere again.
Trees handle QAtoms. QAtoms are based on Atoms and QObject.
-Product Selector tree
-Basket tree
-Right click handled in trees
parent c35b0117
......@@ -56,8 +56,11 @@ class UIProperties:
return self.toolTip
class Atom:
def __init__(self):
def __init__(self,texts=[], icon=""):
self.ui = UIProperties()
self.setTexts(texts)
self.setIcon(icon)
self.id = None #id used in db
def setTexts(self, text):
self.ui.setTexts(text)
......@@ -104,6 +107,17 @@ class Atom:
def getUI(self):
return self.ui
def getId(self):
return self.id
def setId(self, id):
self.id = id
return self
def __eq__(self, key):
raise NotImplementedError('__eq__ not implemented for this class.')
class HappyHours(Atom):
def __init__(self):
......@@ -140,9 +154,6 @@ class User(Atom):
self.balance = None
self.canDrink = None
def setId(self, id):
self.id = id
return self
def setBalance(self, balance):
self.balance = balance
......@@ -152,9 +163,6 @@ class User(Atom):
self.canDrink = canDrink
return self
def getId(self):
return self.id
def getBalance(self):
return self.balance
......@@ -165,7 +173,6 @@ class User(Atom):
class Product(Atom):
def __init__(self):
super().__init__()
self.id = None # int64, used in db
self.name = None # pretty print
self.code = None # short name
self.price = None # The price is either updated when it's in a the selctor, either retreived from database when it's in history
......@@ -173,16 +180,14 @@ class Product(Atom):
self.happyHours = None # List of happy hours
self.category = None # string e.g "Drinks.Alcool.Wine"
def setId(self, id):
self.id = id
return self
def setName(self, name):
self.name = name
return self
def setCode(self, code):
self.code = code
self.setIcon(self.code) # /!\ Maybe this should not hardcoded here
self.setToolTip(self.code) #Same remark
return self
def setPrice(self, price): # price is never a float but something inherited from Decimal (Money.Eur)
......@@ -201,9 +206,6 @@ class Product(Atom):
self.category = category
return self
def getId(self):
return self.id
def getName(self):
return self.name
......@@ -222,6 +224,14 @@ class Product(Atom):
def getCategory(self):
return self.category
def __eq__(self, key):
result = True
result = result and self.getId() == key.getId()
# result = result and self.getName() == key.getName()
# result = result and self.getCode() == key.getCode()
# result = result and self.getPrice() == key.getPrice()
return result
def __repr__(self):
return "Product({0}, {1})".format(self.id,self.name)
......@@ -232,17 +242,12 @@ class Product(Atom):
class Operation(Atom):
def __init__(self):
super().__init__()
self.id = None
self.label = None # human readable description gave by the server
self.refounded = None
# Could be usefull to show where the product has been bought
self.counterId = None
self.date = None
def setId(self, id):
self.id = id
return self
def setLabel(self, label):
self.label = label
return self
......@@ -259,9 +264,6 @@ class Operation(Atom):
self.date = date
return self
def getId(self):
return self.id
def getLabel(self):
return self.label
......@@ -328,20 +330,12 @@ class Refilling(Operation):
class Counter(Atom):
def __init__(self):
super().__init__()
self.id = None
self.name = None
def setId(self, id):
self.id = id
return self
def setName(self, name):
self.name = name
return self
def getId(self):
return self.id
def getName(self):
return self.name
......@@ -350,6 +344,9 @@ class Counter(Atom):
def __str__(self):
return "{0}: {1}".format(self.id,self.name)
def __eq__(self, key):
return key.getId() == self.getId() and key.getName() == self.getName()
class Distribution(Atom):
def __init__(self):
......
......@@ -5,43 +5,27 @@ from grpc import RpcError
import com_pb2
import com_pb2_grpc
from google.protobuf.timestamp_pb2 import Timestamp
from QAtoms import *
from Console import *
from convert import *
# OPTINAL FOR PINGING THE SERVER AND ENSURE IT'S AVAILABLE
import platform # For getting the operating system name
import subprocess # For executing a shell command
def ping(host):
"""
Returns True if host (str) responds to a ping request.
Remember that a host may not respond to a ping (ICMP) request even if the host name is valid.
"""
#def addProduct(product,categoryList):
# try:
# if isinstance(product,dict):
# productDict={categoryList[-1]:product}
# else:
# productDict={categoryList[-1]:[product]}
# return addProduct(productDict,categoryList[:-1])
# except IndexError:
# return product
#def parseProducts(productsReply):
# pbProductList = productsReply.products # get protobuff products
# for i in pbProductList:
# happyHoursList = []
# pbHappyHoursList = i.happy_hours
# for j in pbHappyHoursList:
# newHappyHour = HappyHours()
# newHappyHour.setStart(j.start) #Still need to be converted in QTime
# newHappyHour.setEnd(j.end)
# newHappyHour.setPrice(pb_money_to_eur(j.price)) # Since we choosed a securised money format we need to convert
# happyHoursList.append(newHappyHour)
# newProduct = Product()
# newProduct.setId(i.id)
# newProduct.setName(i.name)
# newProduct.setCode(i.code)
# newProduct.setPrice(pb_money_to_eur(i.default_price))
# newProduct.setHappyHours(happyHoursList)
# newProduct.setCategory(i.category)
#
# Option for the number of packets as a function of
param = '-n' if platform.system().lower()=='windows' else '-c'
# Building the command. Ex: "ping -c 1 google.com"
command = ['ping', param, '1', host]
return subprocess.call(command) == 0
class ClientSingleton(type):
_instance = {}
......@@ -58,6 +42,13 @@ class Client(metaclass=ClientSingleton):
self.channel = grpc.insecure_channel(self.serverAddress)
self.stub = com_pb2_grpc.PaymentProtocolStub(self.channel)
self.now = None
def setServerAddress(self, address):
self.serverAddress = address
#TODO: add ping test here
#TODO: Test address format
self.channel = grpc.insecure_channel(self.serverAddress)
self.stub = com_pb2_grpc.PaymentProtocolStub(self.channel)
def requestBuy(self,**kwargs):
"""
......
......@@ -21,11 +21,17 @@ class Eur(Money):
def __str__(self): # Overloaded function ... allow me to use XX.YY€ instead of EUR XX.YY
print('__str__')
try:
return self.format('fr_FR')
except:
return self.__unicode__()
def __unicode__(self):
return u"{} {:,.2f}".format(self._currency, self._amount)
\ No newline at end of file
return u"{} {:,.2f}".format(self._currency, self._amount)
def __mul__(self, other):
if isinstance(other, Eur):
raise TypeError("multiplication is unsupported between "
"two money objects")
amount = self._amount * other
return self.__class__(amount)
\ No newline at end of file
import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import PyQt5.QtCore
import PyQt5.QtGui
from QManager import *
from QNFC import *
from QDataManager import QDataManager
from QUtils import *
from QItemTree import *
from Client import *
from QRefillerTab import *
from QCounterTab import *
import json
# de transaction
# Shared variables
PWD = os.getcwd() + "/"
# Get item tree
......
......@@ -4,6 +4,18 @@ from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from QUtils import *
from QUIManager import QUIManager
# TODO:SOLVE THIS ARCHITECTURE PROBLEM
# /!\ ARCHITECTURE PROBLEM... MANAGER CAN'T BE CALLED HERE SINCE QATOMS IS IMPORTED IN QMANAGER
# PROPOSAL: MERGE QATOMS AND QMANAGER
# PROPOSAL2: DEFINE UIMANAGER IN QATOMS
########################
####### QT WIDGETS#####
######################
class QDelButton(QToolButton):
......@@ -11,10 +23,16 @@ class QDelButton(QToolButton):
def __init__(self, parent=None):
super().__init__(parent)
uim = QUIManager()
self.row = -1
self.clicked.connect(self.delete)
self.setIcon(QIcon("ressources/icones/delete.png"))
# So normally ui manager should be used here
# but because of the cycle import problem I can't import the ui manager here unless
# I merge QAtoms with QManager...
# self.setIcon(QIcon("ressources/themes/default/ui-icons/delete.png"))
self.setIcon(uim.getIcon("delete"))
self.setIconSize(QSize(32, 32))
self.setFixedSize(QSize(48, 48))
......@@ -32,7 +50,7 @@ class QQuantity(QWidget):
quantityChanged = pyqtSignal()
def __init__(self, parent=None):
def __init__(self, product, parent=None):
super().__init__(parent)
# Definition
......@@ -40,12 +58,13 @@ class QQuantity(QWidget):
self.minusButton = QToolButton()
self.quantityEditLine = QLineEdit()
self.plusButton = QToolButton()
self.quantity = 1
self.quantity = product.getQuantity()
self.product = product
# Settings
self.minusButton.setText("-")
self.quantityEditLine.setText("1")
self.quantityEditLine.setText(str(self.quantity))
self.quantityEditLine.setAlignment(Qt.AlignHCenter)
self.quantityEditLine.setFixedWidth(50)
......@@ -65,24 +84,27 @@ class QQuantity(QWidget):
self.quantityEditLine.editingFinished.connect(self.__noBlank)
def incQuantity(self):
self.quantity += 1
self.product.incQuantity()
self.quantity = self.product.getQuantity()
self.quantityEditLine.setText(str(self.quantity))
self.quantityEditLine.editingFinished.emit()
# self.quantityChanged.emit()
self.quantityChanged.emit()
def decQuantity(self):
self.quantity -= 1
self.product.decQuantity()
self.quantity = self.product.getQuantity()
if self.quantity > 0:
self.quantityEditLine.setText(str(self.quantity))
self.quantityEditLine.editingFinished.emit()
else:
self.quantity = 1
self.product.setQuantity(1)
def editingFinished(self):
try:
self.quantity = int(self.quantityEditLine.text())
self.product.setQuantity(int(self.quantityEditLine.text()))
except:
self.quantity = 1
self.product.setQuantity(1)
self.quantity = self.product.getQuantity()
if self.quantityEditLine.text() != "":
self.quantityEditLine.setText("1")
......@@ -90,7 +112,7 @@ class QQuantity(QWidget):
self.quantityChanged.emit()
else:
self.quantityEditLine.setText("1")
self.quantity = 1
self.product.setQuantity(1)
popUp = QErrorDialog(
"Erreur de saisie",
"Quantité invalide",
......@@ -109,16 +131,98 @@ class QQuantity(QWidget):
def setQuantity(self, qty):
try:
self.quantity = int(qty)
self.product.setQuantity(int(qty))
self.quantityEditLine.setText(str(qty))
self.quantityChanged.emit()
except ValueError:
print("ERROR: ", qte, " is not a number")
print("ERROR: ", qty, " is not a number")
def update(self):
self.quantityEditLine.setText(str(self.product.getQuantity()))
class QUserInfo(QWidget):
def __init___(self, parent=None):
super().__init__(parent)
uim = QUIManager()
center(self)
self.setWindowTitle("Informations utilisateur")
self.setWindowIcon(uim.getIcon("group"))
class QProductInfo(QWidget):
def __init__(self,product, parent=None):
super().__init__(parent)
uim = QUIManager()
#Definition
self.mainLayout = QVBoxLayout()
self.rowInfo = QRowInfo()
#Layout
self.mainLayout.addWidget(self.rowInfo)
self.setLayout(self.mainLayout)
#Settings
self.rowInfo.addRow("Prix", product.getPrice())
self.rowInfo.addRow("Nom", product.getName())
self.rowInfo.addRow("Code", product.getCode())
self.rowInfo.addRow("Id", product.getId())
self.setFixedSize(300,100)
self.setWindowTitle("Informations produit")
self.setWindowIcon(uim.getWindowIcon("product"))
center(self)
class QAtom(QObject, Atom):
def __init__(self):
super().__init__()
####################################
########### QATOMS ##########
##################################
class QAtom(QObject,Atom):
def __init__(self,atom = None):
super().__init__()
self.atomKeys = []
if atom:
self.setAtom(atom)
self.actionDict = {}
def setAtom(self, atom):
if isinstance(atom, Atom):
self.atomKeys = list(vars(atom).keys())
else:
self.atomKeys = atom.getAtomKeys()
selfDict = vars(self) # get the dictionnary representation of the QAtom
for key in self.atomKeys: #for each attribute of the incomming atom
selfDict[key] = vars(atom)[key] #copy it in the QAtom
def getAtom(self):
atom = (type(self).__bases__[1])() #Create an atom that match the base class
#note that it involve that the base class must always be the 2nd base
atomDict = vars(atom)
selfDict = vars(self) #/!\ THIS INVOLVE THAT THIS FUNCTION DON'T RETURN A COPY BUT THE ACTUAL ATOM /!\
# IT'S COOL BECAUSE IT MEANS THAT WE CAN EITHER WORK WITH A COPY OR A POINTER
# BUT WE HAVE TO BE CAREFULL WITH THIS AND USE DEEPCOPY OR NOT...
for key in self.atomKeys:
atomDict[key] = selfDict[key]
#We return actually a copy of the data contained in QAtom
return atom
def getAtomKeys(self):
return self.atomKeys
def getActionDict(self):
return self.actionDict
def setActionDict(self, actionDict):
self.actionDict = actionDict
class QUser(QAtom, User):
......@@ -129,16 +233,52 @@ class QUser(QAtom, User):
class QProduct(QAtom, Product):
def __init__(self):
super().__init__()
deleted = pyqtSignal()
updated = pyqtSignal()
def __init__(self,product):
super().__init__(product)
self.infoPannel = None
self.editPannel = None
self.quantityPannel = QQuantity()
self.quantityPannel = QQuantity(self)
self.delButton = QDelButton()
class QCounter(QAtom, Counter):
self.quantityPannel.quantityChanged.connect(self.update)
self.delButton.clicked.connect(self.delete)
self.actionDict={"Informations produit":{"fct":self.showInfoPannel,"icon":"product"}}
def showInfoPannel(self):
self.infoPannel = QProductInfo(self)
self.infoPannel.show()
def getQuantityPannel(self):
return self.quantityPannel
def getDelButton(self):
return self.delButton
def incQuantity(self):
self.setQuantity(self.getQuantity()+1)
self.update()
return self.getQuantity()
def decQuantity(self):
if self.getQuantity()>1:
self.setQuantity(self.getQuantity()-1)
self.update()
return self.getQuantity()
def update(self):
self.quantityPannel.update()
self.updated.emit()
def delete(self):
self.deleted.emit()
class QCounter(QAtom, Counter):
def __init__(self):
super().__init__()
self.infoPannel = None
......@@ -153,7 +293,6 @@ class QRefilling(QAtom, Refilling):
super().__init__()
self.infoPannel = None
class QDistribution(QAtom, Distribution):
def __init__(self):
super().__init__()
......
......@@ -5,15 +5,14 @@ from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import PyQt5.QtCore
import PyQt5.QtGui
from QManager import *
from QNFC import *
from QDataManager import QDataManager
from QNFCManager import QNFCManager
from QUIManager import QUIManager
from QUtils import *
from QItemTree import *
from Client import *
......@@ -66,7 +65,7 @@ class QCounterTab(QWidget):
self.itemSelector = QProductSelector()
#Mid pannel
self.basket = None
self.basket = QBasket()
#Right pannel
self.rightPannelLayout = QVBoxLayout() # find a better name ~
......@@ -109,6 +108,7 @@ class QCounterTab(QWidget):
#signals
self.itemSelector.itemSelected[Product].connect(self.basket.addProduct)
class QNFCInfo(QWidget):
......
......@@ -2,14 +2,18 @@ from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from Atoms import *
from Client import *
from QNFC import *
#for Data manager, create random machine uid
import uuid
# for QUIManager, find file in ressources
import os
from Console import * #For colored printing
#TEST
import pickle
from pickle import PickleError
# /!\ Managers must always be called in Qt context, so no global variable for data manager...
# the reason is simple, DataManager needs QAtoms, that need Widgets. Widgets can't be create
# if the Qt application did not start yet... It's the best compromise I have found to keep
......@@ -22,9 +26,22 @@ from Console import * #For colored printing
# otherwise you might get a core dump error from Qt
# /!\
#EDIT: NOW MANAGERS ARE NOT DEPENDENT TO QATOMS ANYMORE SO THE TEXT ABOVE IS IRELEVANT BUT IT MUST BE TESTED
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# ________ _____ ______ ________ ________ ________ ________ _______ ________
#|\ __ \|\ _ \ _ \|\ __ \|\ ___ \|\ __ \|\ ____\|\ ___ \ |\ __ \
#\ \ \|\ \ \ \\\__\ \ \ \ \|\ \ \ \\ \ \ \ \|\ \ \ \___|\ \ __/|\ \ \|\ \
# \ \ \\\ \ \ \\|__| \ \ \ __ \ \ \\ \ \ \ __ \ \ \ __\ \ \_|/_\ \ _ _\
# \ \ \\\ \ \ \ \ \ \ \ \ \ \ \ \\ \ \ \ \ \ \ \ \|\ \ \ \_|\ \ \ \\ \|
# \ \_____ \ \__\ \ \__\ \__\ \__\ \__\\ \__\ \__\ \__\ \_______\ \_______\ \__\\ _\
# \|___| \__\|__| \|__|\|__|\|__|\|__| \|__|\|__|\|__|\|_______|\|_______|\|__|\|__|
# \|__|
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Need to be seriously tested !
def parseProductDict(productList: [QProduct]):
def parseProductDict(productList: [Product]):