diff --git a/brmbar3/brmbar-cli.py b/brmbar3/brmbar-cli.py index cb6cb20..4e75325 100755 --- a/brmbar3/brmbar-cli.py +++ b/brmbar3/brmbar-cli.py @@ -15,7 +15,6 @@ active_credit = None for line in sys.stdin: barcode = line.rstrip() - # TODO $neco if barcode[0] == "$": credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000} credit = credits[barcode] @@ -25,7 +24,7 @@ for line in sys.stdin: print("CREDIT " + str(credit)) active_inv_item = None active_credit = credit - continue; + continue if barcode == "SCR": print("SHOW CREDIT") diff --git a/brmbar3/brmbar-gui-qt4.py b/brmbar3/brmbar-gui-qt4.py new file mode 100755 index 0000000..3f53876 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 + +import sys +import psycopg2 + +from PySide import QtCore, QtGui, QtDeclarative + +import brmbar + + +class ShopAdapter(QtCore.QObject): + """ Interface between QML and the brmbar package """ + def __init__(self): + QtCore.QObject.__init__(self) + + @QtCore.Slot(str, result='QVariant') + def barcodeInput(self, barcode): + """ Evaluate barcode received on input + + Normally, we would return just the account object, but + passing that to QML appears to be very non-trivial. + Therefore, we construct a map that we can pass around easily. + We return None on unrecognized barcode. """ + barcode = str(barcode) + if barcode and barcode[0] == "$": + credits = {'$02': 20, '$05': 50, '$10': 100, '$20': 200, '$50': 500, '$1k': 1000} + credit = credits[barcode] + if credit is None: + return None + return { "acctype": "recharge", "amount": str(credit)+".00" } + acct = brmbar.Account.load_by_barcode(db, barcode) + if acct is None: + return None + if acct.acctype == 'debt': + map = acct.__dict__.copy() + map["balance"] = str(acct.balance()) + map["negbalance"] = str(-acct.balance()) + map["negbalance_str"] = acct.negbalance_str() + return map + elif acct.acctype == "inventory": + buy, sell = acct.currency.rates(currency) + map = acct.__dict__.copy() + map["price"] = str(sell) + return map + else: + return None + + @QtCore.Slot('QVariant', 'QVariant', result='QVariant') + def sellItem(self, itemid, userid): + user = brmbar.Account.load(db, id = userid) + shop.sell(item = brmbar.Account.load(db, id = itemid), user = user) + return user.negbalance_str() + + @QtCore.Slot('QVariant', 'QVariant', result='QVariant') + def chargeCredit(self, credit, userid): + user = brmbar.Account.load(db, id = userid) + shop.add_credit(credit = credit, user = user) + return user.negbalance_str() + +db = psycopg2.connect("dbname=brmbar") +shop = brmbar.Shop.new_with_defaults(db) +currency = shop.currency + + +app = QtGui.QApplication(sys.argv) +view = QtDeclarative.QDeclarativeView() + +ctx = view.rootContext() +ctx.setContextProperty('shop', ShopAdapter()) + +view.setSource('brmbar-gui-qt4/main.qml') + +view.show() +app.exec_() diff --git a/brmbar3/brmbar-gui-qt4/BarButton.qml b/brmbar3/brmbar-gui-qt4/BarButton.qml new file mode 100644 index 0000000..a202d4c --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/BarButton.qml @@ -0,0 +1,34 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +Rectangle { + id: rectangle1 + width: 240 + height: 83 + color: "#000000" + border.color: "#ffffff" + + property string text: "Button" + property int fontSize: 46 + + signal buttonClick + onButtonClick: { /* Supplied by component user. */ } + + Text { + id: text1 + color: "#ffffff" + text: parent.text + font.pointSize: 44 + scale: if (!mousearea1.pressed) { 1 } else { 0.95 } + horizontalAlignment: Text.AlignHCenter + anchors.fill: parent + smooth: true + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + id: mousearea1 + anchors.fill: parent + onClicked: buttonClick() + } +} diff --git a/brmbar3/brmbar-gui-qt4/BarClock.qml b/brmbar3/brmbar-gui-qt4/BarClock.qml new file mode 100644 index 0000000..2840857 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/BarClock.qml @@ -0,0 +1,27 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +Rectangle { + id: clock + width: 320 + height: 65 + property variant now: new Date() + property variant textColor: "#000000" + property variant textSize: 12 + Timer { + id: clockUpdater + interval: 1000 // update clock every second + running: true + repeat: true + onTriggered: { + parent.now = new Date() + } + } + Text { + id: clockLabel + anchors.centerIn: parent + text: Qt.formatDateTime(parent.now, "hh:mm:ss") + color: parent.textColor + font.pointSize: parent.textSize + } +} diff --git a/brmbar3/brmbar-gui-qt4/BarTextHint.qml b/brmbar3/brmbar-gui-qt4/BarTextHint.qml new file mode 100644 index 0000000..4ef4e92 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/BarTextHint.qml @@ -0,0 +1,38 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +Item { + id: main_hint + width: 894 + height: 80 + + property variant hint_goal: "" + property variant hint_action: "" + + Text { + id: text1 + x: 0 + y: 0 + color: "#ffffff" + text: parent.hint_goal + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + font.pointSize: 44 + } + + Text { + id: text2 + x: 11 + color: "#40ff5d"; + text: parent.hint_action; + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + transformOrigin: Item.Center; smooth: true; font.bold: false; wrapMode: Text.NoWrap; font.pointSize: 44;horizontalAlignment: Text.AlignHCenter + } +} diff --git a/brmbar3/brmbar-gui-qt4/BarcodeInput.qml b/brmbar3/brmbar-gui-qt4/BarcodeInput.qml new file mode 100644 index 0000000..908902e --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/BarcodeInput.qml @@ -0,0 +1,18 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +TextInput { + id: barcode + x: 433 + y: 65 + width: 80 + height: 100 + color: "#ff6464" + text: "" + transformOrigin: Item.Center + visible: true + opacity: 0 + font.pixelSize: 12 + focus: true + validator: RegExpValidator { regExp: /..*/ } /* non-empty strings; barcode readers send empty lines, ignore these */ +} diff --git a/brmbar3/brmbar-gui-qt4/BasePage.qml b/brmbar3/brmbar-gui-qt4/BasePage.qml new file mode 100644 index 0000000..074c045 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/BasePage.qml @@ -0,0 +1,80 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 +import QtQuick 1.0 + +Rectangle { + width: 1024 + height: 768 + color: "#000000" + Text { + id: title + x: 65 + y: 35 + color: "#71cccc" + text: "brmbar v3" + font.pointSize: 36 + } + BarClock { + id: clock + x: 328 + y: 35 + color: "#000000" + textColor: "#71cccc" + textSize: 36 + } + + Image { + id: image1 + x: 688 + y: 41 + height: 65 + smooth: true + fillMode: Image.PreserveAspectFit + source: "brmlab.svg" + } + + property alias status_text: status_text_id + Text { + id: status_text_id + x: 65 + y: 112 + width: 894 + color: "#ff4444" + text: "" + horizontalAlignment: Text.AlignHCenter + //anchors.horizontalCenter: clock.horizontalCenter + font.pointSize: 36 + + state: "HIDDEN" + opacity: 0 + + states: [ + State { + name: "HIDDEN" + PropertyChanges { target: status_text; opacity: 0 } + }, + State { + name: "VISIBLE" + PropertyChanges { target: status_text; opacity: 100 } + } + ] + + transitions: [ + Transition { + from: "VISIBLE" + to: "HIDDEN" + NumberAnimation { property: "opacity"; duration: 12000; easing.type: Easing.InOutCubic } + } + ] + + function setStatus(statusText, statusColor) { + text = statusText + color = statusColor + state = "VISIBLE" + state = "HIDDEN" + } + function hideStatus() { + state = "HIDDEN" + } + } +} diff --git a/brmbar3/brmbar-gui-qt4/ChargeCredit.qml b/brmbar3/brmbar-gui-qt4/ChargeCredit.qml new file mode 100644 index 0000000..71d277e --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/ChargeCredit.qml @@ -0,0 +1,113 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 +import QtQuick 1.0 + +Item { + id: page + anchors.fill: parent + + property variant username: "" + property variant userdbid: "" + property variant amount: "" + + Text { + id: item_name + x: 65 + y: 156 + width: 537 + height: 160 + color: "#ffffff" + text: parent.username ? parent.username : "Credit charge" + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + font.pointSize: 44 + } + + Text { + id: text3 + x: 611 + y: 156 + height: 160 + width: 348 + color: "#ffff7c" + text: parent.amount + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + font.pointSize: 90 + } + + BarTextHint { + x: 65 + y: 430 + hint_goal: parent.amount ? (parent.username ? "Charge now?" : "Charge user:") : "Charge credit:" + hint_action: !(parent.amount && parent.userdbid) ? "Scan barcode now" : "" + } + + BarcodeInput { + color: "#00ff00" /* just for debugging */ + onAccepted: { + var acct = shop.barcodeInput(text) + text = "" + if (typeof(acct) == "undefined" || (parent.username && acct.acctype != "recharge") || (parent.amount && acct.acctype != "debt")) { + status_text.setStatus("Unknown barcode", "#ff4444") + return + } + if (acct.acctype == "debt") { + username = acct.name + userdbid = acct.id + } else { + amount = acct.amount + } + if (username && amount) { + parent.chargeCredit() + } + } + } + + BarButton { + id: charge_button + x: 65 + y: 582 + width: 360 + text: "Charge" + fontSize: 44 + visible: parent.amount && parent.userdbid + onButtonClick: { + parent.chargeCredit() + } + } + + BarButton { + id: cancel + x: 599 + y: 582 + width: 360 + text: "Cancel" + onButtonClick: { + status_text.setStatus("Charging cancelled", "#ff4444") + loadPage("MainPage") + } + } + + Text { + id: text1 + x: 112 + y: 333 + width: 800 + height: 80 + color: "#ffffff" + text: "Put "+amount+" Kč in the money box now." + visible: amount ? true : false + anchors.horizontalCenterOffset: 0 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.pointSize: 36 + anchors.horizontalCenter: parent.horizontalCenter + } + + function chargeCredit() { + var balance = shop.chargeCredit(amount, userdbid) + status_text.setStatus("Charged! "+username+"'s credit is "+balance+".", "#ffff7c") + loadPage("MainPage") + } +} diff --git a/brmbar3/brmbar-gui-qt4/ItemInfo.qml b/brmbar3/brmbar-gui-qt4/ItemInfo.qml new file mode 100644 index 0000000..3445e7d --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/ItemInfo.qml @@ -0,0 +1,90 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 +import QtQuick 1.0 + +Item { + id: page + anchors.fill: parent + + property variant name: "" + property variant dbid: "" + property variant price: "" + + Text { + id: item_name + x: 65 + y: 156 + width: 537 + height: 160 + color: "#ffffff" + text: parent.name + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + font.pointSize: 44 + } + + Text { + id: text3 + x: 611 + y: 156 + height: 160 + width: 348 + color: "#ffff7c" + text: parent.price + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + font.pointSize: 90 + } + + BarTextHint { + x: 65 + y: 430 + hint_goal: "Buy on credit:" + hint_action: "Scan barcode now" + } + + BarcodeInput { + color: "#00ff00" /* just for debugging */ + onAccepted: { + var acct = shop.barcodeInput(text) + text = "" + if (typeof(acct) == "undefined") { + status_text.setStatus("Unknown barcode", "#ff4444") + return + } + if (acct.acctype != "debt") { + loadPageByAcct(acct) + return + } + var balance = shop.sellItem(dbid, acct.id) + status_text.setStatus("Sold! "+acct.name+"'s credit is "+balance+".", "#ffff7c") + loadPage("MainPage") + } + } + + BarButton { + id: pay_cash + x: 65 + y: 582 + width: 360 + text: "Pay by cash" + fontSize: 44 + onButtonClick: { + // TODO + status_text.setStatus("Sold! Put " + price + " Kč in the money box.", "#ffff7c") + loadPage("MainPage") + } + } + + BarButton { + id: cancel + x: 599 + y: 582 + width: 360 + text: "Cancel" + onButtonClick: { + status_text.setStatus("Transaction cancelled", "#ff4444") + loadPage("MainPage") + } + } +} diff --git a/brmbar3/brmbar-gui-qt4/MainPage.qml b/brmbar3/brmbar-gui-qt4/MainPage.qml new file mode 100644 index 0000000..b93576d --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/MainPage.qml @@ -0,0 +1,63 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 +import QtQuick 1.0 + +Item { + id: page + anchors.fill: parent + + BarTextHint { + x: 65 + y: 234 + hint_goal: "Buy item:" + hint_action: "Scan barcode now" + } + + BarcodeInput { + onAccepted: { + var acct = shop.barcodeInput(text) + text = "" + if (typeof(acct) == "undefined") { + status_text.setStatus("Unknown barcode", "#ff4444") + return + } + loadPageByAcct(acct) + } + } + + BarButton { + id: select_item + x: 65 + y: 430 + width: 360 + text: "Select Item" + fontSize: 44 + } + + BarButton { + id: select_credit_user + x: 599 + y: 430 + width: 360 + text: "Credit" + onButtonClick: { + loadPage("ChargeCredit") + } + } + + BarButton { + id: stock_manager + x: 65 + y: 582 + width: 360 + text: "Stock Mgmt" + } + + BarButton { + id: user_manager + x: 599 + y: 582 + width: 360 + text: "User Mgmt" + } +} diff --git a/brmbar3/brmbar-gui-qt4/UserInfo.qml b/brmbar3/brmbar-gui-qt4/UserInfo.qml new file mode 100644 index 0000000..f54f04b --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/UserInfo.qml @@ -0,0 +1,77 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +Item { + id: page + anchors.fill: parent + + property variant name: "" + property variant dbid: "" + property variant negbalance: "" + + Text { + id: item_name + x: 65 + y: 156 + width: 337 + height: 160 + color: "#ffffff" + text: parent.name + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + font.pointSize: 44 + } + + Text { + id: text3 + x: 411 + y: 156 + height: 160 + width: 548 + color: "#ffff7c" + text: parent.negbalance + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + font.pointSize: 90 + } + + BarcodeInput { + onAccepted: { + var acct = shop.barcodeInput(text) + text = "" + if (typeof(acct) == "undefined") { + status_text.setStatus("Unknown barcode", "#ff4444") + return + } + if (acct.acctype == "recharge") { + loadPage("ChargeCredit", { "username": name, "userdbid": dbid, "amount": acct.amount }) + return + } + + loadPageByAcct(acct) + } + } + + BarButton { + id: charge_credit + x: 65 + y: 582 + width: 360 + text: "Charge" + fontSize: 44 + onButtonClick: { + loadPage("ChargeCredit", { "username": name, "userdbid": dbid }) + } + } + + BarButton { + id: cancel + x: 599 + y: 582 + width: 360 + text: "Main Screen" + onButtonClick: { + loadPage("MainPage") + } + } +} diff --git a/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject b/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject new file mode 100644 index 0000000..2f5a47a --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject @@ -0,0 +1,20 @@ +/* File generated by Qt Creator, version 2.5.0 */ + +import QmlProject 1.1 + +Project { + mainFile: "brmbar-gui-qt4.qml" + + /* Include .qml, .js, and image files from current directory and subdirectories */ + QmlFiles { + directory: "." + } + JavaScriptFiles { + directory: "." + } + ImageFiles { + directory: "." + } + /* List of plugin directories passed to QML runtime */ + // importPaths: [ "../exampleplugin" ] +} diff --git a/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject.user b/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject.user new file mode 100644 index 0000000..eeee7ab --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/brmbar-gui-qt4.qmlproject.user @@ -0,0 +1,121 @@ + + + + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + System + false + 4 + false + true + 1 + true + 0 + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + QML Viewer + QML Viewer + QmlProjectManager.QmlTarget + -1 + -1 + 0 + 0 + 0 + + true + + false + false + false + false + true + 0.01 + 10 + true + 25 + + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + QML Viewer + QmlProjectManager.QmlRunConfiguration + CurrentFile + + 2 + + 3768 + false + true + false + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.EnvironmentId + {a277f310-b549-4ad7-87ca-cd03f76f19ff} + + + ProjectExplorer.Project.Updater.FileVersion + 11 + + diff --git a/brmbar3/brmbar-gui-qt4/brmlab.svg b/brmbar3/brmbar-gui-qt4/brmlab.svg new file mode 100644 index 0000000..d6803f6 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/brmlab.svg @@ -0,0 +1,189 @@ + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/brmbar3/brmbar-gui-qt4/main.qml b/brmbar3/brmbar-gui-qt4/main.qml new file mode 100644 index 0000000..5c43528 --- /dev/null +++ b/brmbar3/brmbar-gui-qt4/main.qml @@ -0,0 +1,28 @@ +// import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 +import QtQuick 1.1 + +BasePage { + id: canvas + + property variant page: Qt.createComponent("MainPage.qml").createObject(canvas) + function loadPage(name, properties) { + status_text.hideStatus() + name += ".qml" + page.destroy(); + if (typeof(properties) == "undefined") { + page = Qt.createComponent(name).createObject(canvas) + } else { + page = Qt.createComponent(name).createObject(canvas, properties) + } + } + + function loadPageByAcct(acct) { + if (acct.acctype == "inventory") { + loadPage("ItemInfo", { name: acct["name"], dbid: acct["id"], price: acct["price"] }) + } else if (acct.acctype == "debt") { + loadPage("UserInfo", { name: acct["name"], dbid: acct["id"], negbalance: acct["negbalance"] }) + } else if (acct.acctype == "recharge") { + loadPage("ChargeCredit", { amount: acct["amount"] }) + } + } +}