Merge branch 'master' of github.com:cryptonomex/graphene

This commit is contained in:
Daniel Larimer 2015-07-27 17:50:24 -04:00
commit fa9b71a463
23 changed files with 287 additions and 131 deletions

View file

@ -15,6 +15,8 @@
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#include <cctype>
#include <graphene/app/api.hpp> #include <graphene/app/api.hpp>
#include <graphene/app/api_access.hpp> #include <graphene/app/api_access.hpp>
#include <graphene/app/application.hpp> #include <graphene/app/application.hpp>

View file

@ -6,8 +6,10 @@ namespace graphene { namespace chain {
void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::public_key& pub, void memo_data::set_message(const fc::ecc::private_key& priv, const fc::ecc::public_key& pub,
const string& msg, uint64_t custom_nonce) const string& msg, uint64_t custom_nonce)
{ {
if( from != public_key_type() ) if( priv != fc::ecc::private_key() && public_key_type(pub) != public_key_type() )
{ {
from = priv.get_public_key();
to = pub;
if( custom_nonce == 0 ) if( custom_nonce == 0 )
{ {
uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0]; uint64_t entropy = fc::sha224::hash(fc::ecc::private_key::generate())._hash[0];

View file

@ -208,18 +208,24 @@ void verify_authority( const vector<operation>& ops, const flat_set<public_key_t
s.approved_by.insert( id ); s.approved_by.insert( id );
for( const auto& auth : other ) for( const auto& auth : other )
{
GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, "Missing Authority", ("auth",auth)("sigs",sigs) ); GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, "Missing Authority", ("auth",auth)("sigs",sigs) );
}
// fetch all of the top level authorities // fetch all of the top level authorities
for( auto id : required_active ) for( auto id : required_active )
{
GRAPHENE_ASSERT( s.check_authority(id) || GRAPHENE_ASSERT( s.check_authority(id) ||
s.check_authority(get_owner(id)), s.check_authority(get_owner(id)),
tx_missing_active_auth, "Missing Active Authority ${id}", ("id",id)("auth",*get_active(id))("owner",*get_owner(id)) ); tx_missing_active_auth, "Missing Active Authority ${id}", ("id",id)("auth",*get_active(id))("owner",*get_owner(id)) );
}
for( auto id : required_owner ) for( auto id : required_owner )
{
GRAPHENE_ASSERT( owner_approvals.find(id) != owner_approvals.end() || GRAPHENE_ASSERT( owner_approvals.find(id) != owner_approvals.end() ||
s.check_authority(get_owner(id)), s.check_authority(get_owner(id)),
tx_missing_other_auth, "Missing Owner Authority ${id}", ("id",id)("auth",*get_owner(id)) ); tx_missing_other_auth, "Missing Owner Authority ${id}", ("id",id)("auth",*get_owner(id)) );
}
FC_ASSERT( !s.remove_unused_signatures(), "Unnecessary signatures detected" ); FC_ASSERT( !s.remove_unused_signatures(), "Unnecessary signatures detected" );
} FC_CAPTURE_AND_RETHROW( (ops)(sigs) ) } } FC_CAPTURE_AND_RETHROW( (ops)(sigs) ) }
@ -230,7 +236,9 @@ flat_set<public_key_type> signed_transaction::get_signature_keys()const
auto d = digest(); auto d = digest();
flat_set<public_key_type> result; flat_set<public_key_type> result;
for( const auto& sig : signatures ) for( const auto& sig : signatures )
{
FC_ASSERT( result.insert( fc::ecc::public_key(sig,d) ).second, "Duplicate Signature detected" ); FC_ASSERT( result.insert( fc::ecc::public_key(sig,d) ).second, "Duplicate Signature detected" );
}
return result; return result;
} FC_CAPTURE_AND_RETHROW() } } FC_CAPTURE_AND_RETHROW() }

@ -1 +1 @@
Subproject commit e9eeb3300c59bca19b8c79c14ed85226d2262c63 Subproject commit a31f0f503df4d70ebb5960803c7092dc170bee92

View file

@ -2983,8 +2983,10 @@ blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
if( broadcast ) if( broadcast )
{ {
for( const auto& out : confirm.outputs ) for( const auto& out : confirm.outputs )
{
try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, "" ); } catch ( ... ){} try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, "" ); } catch ( ... ){}
} }
}
return confirm; return confirm;
} FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) } } FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) }
@ -3070,8 +3072,10 @@ blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name
if( broadcast ) if( broadcast )
{ {
for( const auto& out : confirm.outputs ) for( const auto& out : confirm.outputs )
{
try { receive_blind_transfer( out.confirmation_receipt, "@"+from_account.name, "from @"+from_account.name ); } catch ( ... ){} try { receive_blind_transfer( out.confirmation_receipt, "@"+from_account.name, "from @"+from_account.name ); } catch ( ... ){}
} }
}
return confirm; return confirm;
} FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) } } FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) }

View file

@ -51,7 +51,6 @@ public:
void update(const account_balance_object& balance); void update(const account_balance_object& balance);
/** /**
* Anything greater than 1.0 means full authority. * Anything greater than 1.0 means full authority.
* Anything between (0 and 1.0) means partial authority * Anything between (0 and 1.0) means partial authority

View file

@ -4,6 +4,13 @@
#include <QVariant> #include <QVariant>
QString Asset::formatAmount(qint64 amount) const
{
graphene::chain::asset_object ao;
ao.precision = m_precision;
return QString::fromStdString(ao.amount_to_string(amount));
}
void Asset::update(const graphene::chain::asset_object& asset) void Asset::update(const graphene::chain::asset_object& asset)
{ {
if (asset.id.instance() != id()) if (asset.id.instance() != id())

View file

@ -30,6 +30,8 @@ public:
power *= 10; power *= 10;
return power; return power;
} }
/// Given an amount like 123401, return "1234.01"
Q_INVOKABLE QString formatAmount(qint64 amount) const;
void update(const graphene::chain::asset_object& asset); void update(const graphene::chain::asset_object& asset);

View file

@ -2,6 +2,7 @@
#include "ChainDataModel.hpp" #include "ChainDataModel.hpp"
#include "Wallet.hpp" #include "Wallet.hpp"
#include "Operations.hpp" #include "Operations.hpp"
#include "Transaction.hpp"
#include <graphene/app/api.hpp> #include <graphene/app/api.hpp>
@ -74,6 +75,11 @@ void GrapheneApplication::start(QString apiurl, QString user, QString pass)
} }
} }
Transaction* GrapheneApplication::createTransaction() const
{
return new Transaction;
}
Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const
{ {
func(); func();

View file

@ -14,6 +14,8 @@ class websocket_client;
class ChainDataModel; class ChainDataModel;
class OperationBuilder; class OperationBuilder;
class OperationBase;
class Transaction;
class Wallet; class Wallet;
class GrapheneApplication : public QObject { class GrapheneApplication : public QObject {
Q_OBJECT Q_OBJECT
@ -62,6 +64,9 @@ public:
return m_isConnected; return m_isConnected;
} }
/// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction.
Q_INVOKABLE Transaction* createTransaction() const;
Q_SIGNALS: Q_SIGNALS:
void exceptionThrown(QString message); void exceptionThrown(QString message);
void loginFailed(); void loginFailed();

View file

@ -5,8 +5,7 @@
TransferOperation* OperationBuilder::transfer(ObjectId sender, ObjectId receiver, qint64 amount, TransferOperation* OperationBuilder::transfer(ObjectId sender, ObjectId receiver, qint64 amount,
ObjectId amountType, QString memo, ObjectId feeType) ObjectId amountType, QString memo, ObjectId feeType)
{ {
static fc::ecc::private_key dummyPrivate = fc::ecc::private_key::generate(); try {
static fc::ecc::public_key dummyPublic = fc::ecc::private_key::generate().get_public_key();
TransferOperation* op = new TransferOperation; TransferOperation* op = new TransferOperation;
op->setSender(sender); op->setSender(sender);
op->setReceiver(receiver); op->setReceiver(receiver);
@ -15,8 +14,30 @@ TransferOperation* OperationBuilder::transfer(ObjectId sender, ObjectId receiver
op->setMemo(memo); op->setMemo(memo);
op->setFeeType(feeType); op->setFeeType(feeType);
auto feeParameters = model.global_properties().parameters.current_fees->get<graphene::chain::transfer_operation>(); auto feeParameters = model.global_properties().parameters.current_fees->get<graphene::chain::transfer_operation>();
op->operation().memo = graphene::chain::memo_data();
op->operation().memo->set_message(dummyPrivate, dummyPublic, memo.toStdString());
op->setFee(op->operation().calculate_fee(feeParameters).value); op->setFee(op->operation().calculate_fee(feeParameters).value);
return op; return op;
} catch (const fc::exception& e) {
qDebug() << e.to_detail_string().c_str();
return nullptr;
}
}
QString TransferOperation::memo() const {
if (!m_op.memo)
return QString::null;
QString memo = QString::fromStdString(m_op.memo->get_message({}, {}));
while (memo.endsWith('\0'))
memo.chop(1);
return memo;
}
void TransferOperation::setMemo(QString memo) {
if (memo == this->memo())
return;
if (!m_op.memo)
m_op.memo = graphene::chain::memo_data();
while (memo.size() % 32)
memo.append('\0');
m_op.memo->set_message({}, {}, memo.toStdString());
Q_EMIT memoChanged();
} }

View file

@ -37,7 +37,6 @@ class TransferOperation : public OperationBase {
Q_PROPERTY(QString memo READ memo WRITE setMemo NOTIFY memoChanged) Q_PROPERTY(QString memo READ memo WRITE setMemo NOTIFY memoChanged)
graphene::chain::transfer_operation m_op; graphene::chain::transfer_operation m_op;
QString m_memo;
public: public:
TransferOperation(){} TransferOperation(){}
@ -57,9 +56,9 @@ public:
ObjectId receiver() const { return m_op.to.instance.value; } ObjectId receiver() const { return m_op.to.instance.value; }
qint64 amount() const { return m_op.amount.amount.value; } qint64 amount() const { return m_op.amount.amount.value; }
ObjectId amountType() const { return m_op.amount.asset_id.instance.value; } ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
/// This does not deal with encrypted memos. The memo stored here is unencrypted, and does not get stored in the /// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
/// underlying graphene operation. The encryption and storage steps must be handled elsewhere. /// performed elsewhere.
QString memo() const { return m_memo; } QString memo() const;
const graphene::chain::transfer_operation& operation() const { return m_op; } const graphene::chain::transfer_operation& operation() const { return m_op; }
graphene::chain::transfer_operation& operation() { return m_op; } graphene::chain::transfer_operation& operation() { return m_op; }
@ -101,14 +100,9 @@ public Q_SLOTS:
m_op.amount.asset_id = arg; m_op.amount.asset_id = arg;
Q_EMIT amountTypeChanged(); Q_EMIT amountTypeChanged();
} }
/// This does not deal with encrypted memos. The memo stored here is unencrypted, and does not get stored in the /// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
/// underlying graphene operation. The encryption and storage steps must be handled elsewhere. /// performed elsewhere.
void setMemo(QString memo) { void setMemo(QString memo);
if (memo == m_memo)
return;
m_memo = memo;
Q_EMIT memoChanged();
}
Q_SIGNALS: Q_SIGNALS:
void feeChanged(); void feeChanged();
@ -136,7 +130,7 @@ public:
OperationBuilder(ChainDataModel& model, QObject* parent = nullptr) OperationBuilder(ChainDataModel& model, QObject* parent = nullptr)
: QObject(parent), model(model){} : QObject(parent), model(model){}
Q_INVOKABLE TransferOperation* transfer(ObjectId sender, ObjectId receiver, Q_INVOKABLE TransferOperation* transfer(ObjectId sender, ObjectId receiver, qint64 amount,
qint64 amount, ObjectId amountType, QString memo, ObjectId feeType); ObjectId amountType, QString memo, ObjectId feeType);
}; };

View file

@ -43,6 +43,11 @@ OperationBase* Transaction::operationAt(int index) const {
void Transaction::appendOperation(OperationBase* op) void Transaction::appendOperation(OperationBase* op)
{ {
if (op == nullptr)
{
qWarning("Unable to append null operation to transaction");
return;
}
op->setParent(this); op->setParent(this);
m_transaction.operations.push_back(op->genericOperation()); m_transaction.operations.push_back(op->genericOperation());
Q_EMIT operationsChanged(); Q_EMIT operationsChanged();

View file

@ -50,6 +50,6 @@ private:
Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged) Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged)
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged) Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
Status m_status; Status m_status = Unbroadcasted;
graphene::chain::transaction m_transaction; graphene::chain::transaction m_transaction;
}; };

View file

@ -20,6 +20,9 @@ QML_DECLARE_TYPE(Crypto)
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
#ifndef NDEBUG
QQmlDebuggingEnabler enabler;
#endif
fc::thread::current().set_name("main"); fc::thread::current().set_name("main");
QApplication app(argc, argv); QApplication app(argc, argv);
app.setApplicationName("Graphene Client"); app.setApplicationName("Graphene Client");
@ -28,6 +31,7 @@ int main(int argc, char *argv[])
qRegisterMetaType<std::function<void()>>(); qRegisterMetaType<std::function<void()>>();
qRegisterMetaType<ObjectId>(); qRegisterMetaType<ObjectId>();
qRegisterMetaType<QList<OperationBase*>>();
qmlRegisterType<Asset>("Graphene.Client", 0, 1, "Asset"); qmlRegisterType<Asset>("Graphene.Client", 0, 1, "Asset");
qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance"); qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance");
@ -49,7 +53,6 @@ int main(int argc, char *argv[])
#ifdef NDEBUG #ifdef NDEBUG
engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
#else #else
QQmlDebuggingEnabler enabler;
engine.load(QUrl(QStringLiteral("qml/main.qml"))); engine.load(QUrl(QStringLiteral("qml/main.qml")));
#endif #endif

View file

@ -0,0 +1,37 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import Graphene.Client 0.1
import "."
/**
* Base for all forms
*
* This base contains all of the properties, slots, and signals which all forms are expected to expose. It also
* automatically lays its children out in a ColumnLayout
*/
Rectangle {
anchors.fill: parent
/// Reference to the GrapheneApplication object
property GrapheneApplication app
/// Parent should trigger this signal to notify the form that it is about to be displayed
/// See specific form for the argument semantics
signal display(var arg)
/// Emitted when the form is canceled -- see specific form for the argument semantics
signal canceled(var arg)
/// Emitted when the form is completed -- see specific form for the argument semantics
signal completed(var arg)
default property alias childItems: childLayout.data
ColumnLayout {
id: childLayout
anchors.centerIn: parent
width: parent.width - Scaling.cm(2)
spacing: Scaling.mm(5)
}
}

View file

@ -33,6 +33,8 @@ Rectangle {
form.completed.connect(function(){state = "HIDDEN"; internal.callbackArgs = arguments}) form.completed.connect(function(){state = "HIDDEN"; internal.callbackArgs = arguments})
if (closedCallback instanceof Function) if (closedCallback instanceof Function)
internal.callback = closedCallback internal.callback = closedCallback
// Notify the form that it's about to go live
form.display({})
state = "SHOWN" state = "SHOWN"
} }

View file

@ -8,9 +8,10 @@ Flipable {
property Component frontComponent property Component frontComponent
property Component backComponent property Component backComponent
property GrapheneApplication app
signal canceled signal display(var arg)
signal completed signal canceled(var arg)
signal completed(var arg)
property bool flipped: false property bool flipped: false
@ -19,13 +20,11 @@ Flipable {
front = frontComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return !flipped})}) front = frontComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return !flipped})})
front.canceled.connect(function() { canceled.apply(this, arguments) }) front.canceled.connect(function() { canceled.apply(this, arguments) })
front.completed.connect(function() { front.completed.connect(function() {
if (back.hasOwnProperty("arguments")) back.display.apply(this, arguments)
back.arguments = arguments
flipped = true flipped = true
}) })
back.canceled.connect(function() { back.canceled.connect(function() {
if (front.hasOwnProperty("arguments")) front.display.apply(this, arguments)
front.arguments = arguments
flipped = false flipped = false
}) })
back.completed.connect(function() { completed.apply(this, arguments) }) back.completed.connect(function() { completed.apply(this, arguments) })
@ -35,8 +34,10 @@ Flipable {
id: rotation id: rotation
origin.x: flipable.width/2 origin.x: flipable.width/2
origin.y: flipable.height/2 origin.y: flipable.height/2
axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis // set axis.y to 1 to rotate around y-axis
angle: 0 // the default angle axis.x: 0; axis.y: 1; axis.z: 0
// the default angle
angle: 0
} }
states: State { states: State {

View file

@ -0,0 +1,67 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import Graphene.Client 0.1
import "."
/**
* This is the form for previewing and approving a transaction prior to broadcasting it
*
* The arguments property should be populated with an Array of operations. These operations will be used to create a
* Transaction, display it, and get confirmation to sign and broadcast it. This form will populate the transaction with
* the operations and expiration details, sign it, and pass the signed transaction through the completed signal.
*/
FormBase {
id: base
property Transaction trx
Component.onCompleted: console.log("Made a transaction confirmation form")
Component.onDestruction: console.log("Destroyed a transaction confirmation form")
onDisplay: {
trx = app.createTransaction()
console.log(JSON.stringify(arg))
for (var op in arg)
trx.appendOperation(arg[op])
console.log(JSON.stringify(trx))
}
Component {
id: transactionDelegate
Rectangle {
width: Scaling.cm(10)
height: childrenRect.height + Scaling.cm(1)
radius: Scaling.mm(3)
color: "#EEEEEE"
border.width: Scaling.mm(.25)
border.color: "black"
Column {
y: Scaling.cm(.5)
x: y
width: parent.width - Scaling.cm(1)
Repeater {
model: trx.operations
Label {
property Asset transferAsset: app.model.getAsset(modelData.amountType)
property Asset feeAsset: app.model.getAsset(modelData.feeType)
text: qsTr("Transfer %1 %2 from %3 to %4\nFee: %5 %6").arg(transferAsset.formatAmount(modelData.amount))
.arg(transferAsset.symbol)
.arg(app.model.getAccount(modelData.sender).name)
.arg(app.model.getAccount(modelData.receiver).name)
.arg(feeAsset.formatAmount(modelData.fee))
.arg(feeAsset.symbol)
}
}
}
}
}
Loader {
sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined
}
}

View file

@ -10,13 +10,8 @@ import "."
/** /**
* This is the form for transferring some amount of asset from one account to another. * This is the form for transferring some amount of asset from one account to another.
*/ */
Rectangle { FormBase {
id: root id: base
anchors.fill: parent
property GrapheneApplication app
signal canceled
signal completed(TransferOperation op)
/// The Account object for the sender /// The Account object for the sender
property alias senderAccount: senderPicker.account property alias senderAccount: senderPicker.account
@ -34,11 +29,6 @@ Rectangle {
Component.onCompleted: console.log("Made a transfer form") Component.onCompleted: console.log("Made a transfer form")
Component.onDestruction: console.log("Destroyed a transfer form") Component.onDestruction: console.log("Destroyed a transfer form")
ColumnLayout {
anchors.centerIn: parent
width: parent.width - Scaling.cm(2)
spacing: Scaling.mm(5)
AccountPicker { AccountPicker {
id: senderPicker id: senderPicker
// The senderPicker is really the heart of the form. Everything else in the form adjusts based on the account // The senderPicker is really the heart of the form. Everything else in the form adjusts based on the account
@ -47,7 +37,7 @@ Rectangle {
// to have a maximum value equal to the account's balance in that asset. The transfer button enables only when // to have a maximum value equal to the account's balance in that asset. The transfer button enables only when
// both accounts are set, and a nonzero amount is selected to be transferred. // both accounts are set, and a nonzero amount is selected to be transferred.
app: root.app app: base.app
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: Scaling.cm(5) Layout.minimumWidth: Scaling.cm(5)
Component.onCompleted: setFocus() Component.onCompleted: setFocus()
@ -60,7 +50,7 @@ Rectangle {
} }
AccountPicker { AccountPicker {
id: recipientPicker id: recipientPicker
app: root.app app: base.app
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumWidth: Scaling.cm(5) Layout.minimumWidth: Scaling.cm(5)
placeholderText: qsTr("Recipient") placeholderText: qsTr("Recipient")
@ -116,8 +106,7 @@ Rectangle {
id: transferButton id: transferButton
text: qsTr("Transfer") text: qsTr("Transfer")
enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
onClicked: completed(operation) onClicked: completed([operation()])
}
} }
} }
} }

View file

@ -63,7 +63,7 @@ ApplicationWindow {
onClicked: { onClicked: {
var front = Qt.createComponent("TransferForm.qml") var front = Qt.createComponent("TransferForm.qml")
// TODO: make back into a preview and confirm dialog // TODO: make back into a preview and confirm dialog
var back = Qt.createComponent("TransferForm.qml") var back = Qt.createComponent("TransactionConfirmationForm.qml")
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back}, formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
function() { function() {
console.log("Closed form") console.log("Closed form")

View file

@ -1,7 +1,9 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
<file>main.qml</file> <file>main.qml</file>
<file>FormBase.qml</file>
<file>TransferForm.qml</file> <file>TransferForm.qml</file>
<file>TransactionConfirmationForm.qml</file>
<file>FormBox.qml</file> <file>FormBox.qml</file>
<file>FormFlipper.qml</file> <file>FormFlipper.qml</file>
<file>Scaling.qml</file> <file>Scaling.qml</file>