Skip to content

Add ChainModel #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ QT_FORMS_UI = \
qt/forms/transactiondescdialog.ui

QT_MOC_CPP = \
qml/moc_chainmodel.cpp \
qml/moc_engine.cpp \
qml/moc_nodemodel.cpp \
qt/moc_addressbookpage.cpp \
qt/moc_addresstablemodel.cpp \
Expand Down Expand Up @@ -108,6 +110,8 @@ QT_QRC_LOCALE = qt/bitcoin_locale.qrc

BITCOIN_QT_H = \
qml/bitcoin.h \
qml/chainmodel.h \
qml/engine.h \
qml/imageprovider.h \
qml/nodemodel.h \
qml/util.h \
Expand Down Expand Up @@ -286,6 +290,8 @@ BITCOIN_QT_WALLET_CPP = \

BITCOIN_QML_BASE_CPP = \
qml/bitcoin.cpp \
qml/chainmodel.cpp \
qml/engine.cpp \
qml/imageprovider.cpp \
qml/nodemodel.cpp \
qml/util.cpp
Expand All @@ -298,6 +304,9 @@ QML_QRC_CPP = qml/qrc_bitcoin.cpp
QML_QRC = qml/bitcoin_qml.qrc
QML_RES_QML = \
qml/components/BlockCounter.qml \
qml/components/BlocksListView.qml \
qml/components/ConnectionOptions.qml \
qml/controls/OptionButton.qml \
qml/pages/initerrormessage.qml \
qml/pages/stub.qml

Expand Down Expand Up @@ -441,7 +450,7 @@ ui_%.h: %.ui
$(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(MOC_DEFS) $< > $@

moc_%.cpp: %.h
$(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(MOC_DEFS) $< > $@
$(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(QT_QUICK_CFLAGS) $(MOC_DEFS) $< > $@

%.qm: %.ts
@test -f $(LRELEASE)
Expand Down
21 changes: 11 additions & 10 deletions src/qml/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include <node/context.h>
#include <node/ui_interface.h>
#include <noui.h>
#include <qml/chainmodel.h>
#include <qml/engine.h>
#include <qml/imageprovider.h>
#include <qml/nodemodel.h>
#include <qml/util.h>
Expand All @@ -30,10 +32,10 @@

#include <QDebug>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QString>
#include <QStyleHints>
#include <QUrl>

QT_BEGIN_NAMESPACE
Expand Down Expand Up @@ -100,6 +102,8 @@ int QmlGuiMain(int argc, char* argv[])
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);

app.styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls);

auto handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox);

NodeContext node_context;
Expand Down Expand Up @@ -161,30 +165,27 @@ int QmlGuiMain(int argc, char* argv[])

handler_message_box.disconnect();

NodeModel node_model{*node};
InitExecutor init_executor{*node};
QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize);
QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown);
QObject::connect(&init_executor, &InitExecutor::initializeResult, &node_model, &NodeModel::initializeResult);
QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection);
// QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);

qGuiApp->setQuitOnLastWindowClosed(false);
QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] {
node->startShutdown();
node_model.startNodeShutdown();
init_executor.shutdown();
});

GUIUtil::LoadFont(":/fonts/inter/regular");
GUIUtil::LoadFont(":/fonts/inter/semibold");

QQmlApplicationEngine engine;
qmlRegisterType<ChainModel>("BitcoinCore", 1, 0, "ChainModel");
qmlRegisterType<NodeModel>("BitcoinCore", 1, 0, "NodeModel");

Engine engine(*node);

QScopedPointer<const NetworkStyle> network_style{NetworkStyle::instantiate(Params().NetworkIDString())};
assert(!network_style.isNull());
engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()});

engine.rootContext()->setContextProperty("nodeModel", &node_model);
engine.rootContext()->setContextProperty("initExecutor", &init_executor);

engine.load(QUrl(QStringLiteral("qrc:///qml/pages/stub.qml")));
if (engine.rootObjects().isEmpty()) {
Expand Down
3 changes: 3 additions & 0 deletions src/qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/qml">
<file>controls/OptionButton.qml</file>
<file>components/BlocksListView.qml</file>
<file>components/BlockCounter.qml</file>
<file>components/ConnectionOptions.qml</file>
<file>pages/initerrormessage.qml</file>
<file>pages/stub.qml</file>
</qresource>
Expand Down
94 changes: 94 additions & 0 deletions src/qml/chainmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qml/chainmodel.h>

#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <qml/engine.h>
#include <qt/guiutil.h>

#include <vector>

struct Block
{
const int height;
const std::string hash;
};

struct ChainModelPrivate
{
std::unique_ptr<interfaces::Handler> handler_notify_block_tip;
std::vector<Block> blocks;
};

ChainModel::ChainModel(QObject* parent)
: QAbstractListModel(parent)
, d(new ChainModelPrivate)
{
}

ChainModel::~ChainModel()
{
}

void ChainModel::classBegin()
{
}

void ChainModel::componentComplete()
{
assert(!d->handler_notify_block_tip);
d->handler_notify_block_tip = Engine::node(this).handleNotifyBlockTip(
[this](SynchronizationState state, interfaces::BlockTip tip, double verification_progress) {
// TODO: update existing model incrementally instead of reset
GUIUtil::ObjectInvoke(this, [this] {
beginResetModel();
d->blocks.clear();
endResetModel();
});
});
}

QHash<int, QByteArray> ChainModel::roleNames() const
{
return {
{ BlockHeightRole, "blockHeight" },
{ BlockHashRole, "blockHash" }
};
}

bool ChainModel::canFetchMore(const QModelIndex&) const
{
return d->blocks.size() == 0 || d->blocks[0].height > 0;
}

void ChainModel::fetchMore(const QModelIndex& parent)
{
auto& chain = Engine::chain(this);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add qDebug() << Q_FUNC_INFO; while testing (scrolling the list) to see how lazy loading works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanofsky using interfaces::Chain here.

int height = d->blocks.size() > 0 ? d->blocks[d->blocks.size() - 1].height - 1 : *chain.getHeight();
// TODO: make page size configurable
// TODO: refactor to call beginInsertRows before the loop
for (int count = 10; count > 0 && height >= 0; --count, --height) {
const auto hash = chain.getBlockHash(height).ToString();
beginInsertRows(QModelIndex(), d->blocks.size(), d->blocks.size());
d->blocks.push_back({height, hash});
endInsertRows();
}
}

int ChainModel::rowCount(const QModelIndex& parent) const
{
return d->blocks.size();
}

QVariant ChainModel::data(const QModelIndex& index, int role) const
{
switch (role) {
case BlockHeightRole: return d->blocks[index.row()].height;
case BlockHashRole: return QString::fromStdString(d->blocks[index.row()].hash);
} // no default case, so the compiler can warn about missing cases
assert(false);
}
41 changes: 41 additions & 0 deletions src/qml/chainmodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_QML_CHAINMODEL_H
#define BITCOIN_QML_CHAINMODEL_H

#include <memory>

#include <QAbstractListModel>
#include <QQmlParserStatus>

class ChainModelPrivate;
class ChainModel : public QAbstractListModel, public QQmlParserStatus
{
Q_OBJECT
Q_INTERFACES(QQmlParserStatus)
std::unique_ptr<ChainModelPrivate> d;

public:
enum Role {
BlockHeightRole = Qt::UserRole + 1,
BlockHashRole,
};

explicit ChainModel(QObject* parent = nullptr);
~ChainModel();

// QQmlParserStatus
void classBegin() override;
void componentComplete() override;

// QAbstractListModel
QHash<int, QByteArray> roleNames() const override;
bool canFetchMore(const QModelIndex& parent) const override;
void fetchMore(const QModelIndex& parent) override;
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
};

#endif // BITCOIN_QML_CHAINMODEL_H
2 changes: 1 addition & 1 deletion src/qml/components/BlockCounter.qml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ Label {
verticalAlignment: Text.AlignVCenter
font.family: "Inter"
font.styleName: "Semi Bold"
font.pixelSize: height / 3
font.pixelSize: 20
text: blockHeight
}
45 changes: 45 additions & 0 deletions src/qml/components/BlocksListView.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

import BitcoinCore 1.0
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.11
import "../controls"

ListView {
spacing: 15
model: ChainModel {
}
delegate: ItemDelegate {
id: delegate
padding: 15
background: Rectangle {
border.width: 1
border.color: delegate.hovered ? "white" : "#999999"
radius: 10
color: "transparent"
}
contentItem: ColumnLayout {
spacing: 3
Label {
color: "white"
font.family: "Inter"
font.styleName: "Regular"
font.pixelSize: 28
text: qsTrId('Block #%1').arg(blockHeight)
}
Label {
Layout.fillWidth: true
Layout.preferredWidth: 0
color: "white"
elide: Text.ElideRight
font.family: "Inter"
font.styleName: "Regular"
font.pixelSize: 13
text: blockHash
}
}
}
}
39 changes: 39 additions & 0 deletions src/qml/components/ConnectionOptions.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2021 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.11
import "../controls"

ColumnLayout {
spacing: 15

ButtonGroup {
id: group
}

OptionButton {
ButtonGroup.group: group
Layout.fillWidth: true
text: qsTr("Fast always on")
description: qsTr("Loads quickly at all times and uses as much cellular data as needed.")
recommended: true
}

OptionButton {
ButtonGroup.group: group
Layout.fillWidth: true
checked: true
text: qsTr("Slow always on")
description: qsTr("Loads quickly at all times and uses as much cellular data as needed.")
}

OptionButton {
ButtonGroup.group: group
Layout.fillWidth: true
text: qsTr("Only when on Wi-Fi")
description: qsTr("Loads quickly when on wi-fi and pauses when on cellular data.")
}
}
Loading