Mercurial > hg > bitcoin
changeset 1619:0ad7bd17b899 draft
Merge pull request #629 from sje397/master
QR Code generation via libqrencode
author | Wladimir J. van der Laan <laanwj@gmail.com> |
---|---|
date | Fri, 23 Dec 2011 02:50:28 -0800 |
parents | e16276182f0f (current diff) fb1b855c0484 (diff) |
children | 505d68988db2 47c9a5d98658 |
files | bitcoin-qt.pro src/makefile.osx |
diffstat | 13 files changed, 434 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -19,6 +19,14 @@ MOC_DIR = build UI_DIR = build +# use: qmake "USE_QRCODE=1" +# libqrencode (http://fukuchi.org/works/qrencode/index.en.html) must be installed for support +contains(USE_QRCODE, 1) { + message(Building with QRCode support) + DEFINES += USE_QRCODE + LIBS += -lqrencode +} + # use: qmake "RELEASE=1" contains(RELEASE, 1) { # Mac: compile for maximum compatibility (10.5, 32-bit) @@ -199,6 +207,12 @@ src/qt/forms/sendcoinsentry.ui \ src/qt/forms/askpassphrasedialog.ui +contains(USE_QRCODE, 1) { +HEADERS += src/qt/qrcodedialog.h +SOURCES += src/qt/qrcodedialog.cpp +FORMS += src/qt/forms/qrcodedialog.ui +} + CODECFORTR = UTF-8 # for lrelease/lupdate
--- a/contrib/gitian-descriptors/gitian.yml +++ b/contrib/gitian-descriptors/gitian.yml @@ -16,6 +16,7 @@ - "libssl-dev" - "git-core" - "unzip" +- "qrencode" reference_datetime: "2011-01-30 00:00:00" remotes: - "url": "https://github.com/bitcoin/bitcoin.git"
--- a/doc/build-osx.txt +++ b/doc/build-osx.txt @@ -43,6 +43,9 @@ (this will be unnecessary soon, you will just port install miniupnpc along with the rest of the dependencies). +Optionally install qrencode (and set USE_QRCODE=1): +sudo port install qrencode + 4. Now you should be able to build bitcoind: cd bitcoin/src
--- a/doc/build-unix.txt +++ b/doc/build-unix.txt @@ -23,12 +23,13 @@ Dependencies ------------ - Library Purpose Description - ------- ------- ----------- - libssl SSL Support Secure communications - libdb4.8 Berkeley DB Blockchain & wallet storage - libboost Boost C++ Library - miniupnpc UPnP Support Optional firewall-jumping support + Library Purpose Description + ------- ------- ----------- + libssl SSL Support Secure communications + libdb4.8 Berkeley DB Blockchain & wallet storage + libboost Boost C++ Library + miniupnpc UPnP Support Optional firewall-jumping support + libqrencode QRCode generation Optional QRCode generation miniupnpc may be used for UPnP port mapping. It can be downloaded from http://miniupnp.tuxfamily.org/files/. UPnP support is compiled in and @@ -37,6 +38,12 @@ USE_UPNP=0 (the default) UPnP support turned off by default at runtime USE_UPNP=1 UPnP support turned on by default at runtime +libqrencode may be used for QRCode image generation. It can be downloaded +from http://fukuchi.org/works/qrencode/index.html.en, or installed via +your package manager. Set USE_QRCODE to control this: + USE_QRCODE=0 (the default) No QRCode support - libarcode not required + USE_QRCODE=1 QRCode support enabled + Licenses of statically linked libraries: Berkeley DB New BSD license with additional requirement that linked software must be free open source @@ -50,7 +57,6 @@ Boost 1.37 miniupnpc 1.6 - Dependency Build Instructions: Ubuntu & Debian ---------------------------------------------- sudo apt-get install build-essential
--- a/src/makefile.osx +++ b/src/makefile.osx @@ -96,6 +96,10 @@ endif endif +ifdef USE_QRCODE + DEFS += -DUSE_QRCODE=$(USE_QRCODE) + LIBS += -lqrencode +endif all: bitcoind
--- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -10,6 +10,10 @@ #include <QFileDialog> #include <QMessageBox> +#ifdef USE_QRCODE +#include "qrcodedialog.h" +#endif + AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) : QDialog(parent), ui(new Ui::AddressBookPage), @@ -25,6 +29,10 @@ ui->deleteButton->setIcon(QIcon()); #endif +#ifndef USE_QRCODE + ui->showQRCode->setVisible(false); +#endif + switch(mode) { case ForSending: @@ -169,10 +177,12 @@ break; } ui->copyToClipboard->setEnabled(true); + ui->showQRCode->setEnabled(true); } else { ui->deleteButton->setEnabled(false); + ui->showQRCode->setEnabled(false); ui->copyToClipboard->setEnabled(false); } } @@ -227,3 +237,23 @@ QMessageBox::Abort, QMessageBox::Abort); } } + +void AddressBookPage::on_showQRCode_clicked() +{ +#ifdef USE_QRCODE + QTableView *table = ui->tableView; + QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address); + + + QRCodeDialog *d; + foreach (QModelIndex index, indexes) + { + QString address = index.data().toString(), + label = index.sibling(index.row(), 0).data().toString(), + title = QString("%1 << %2 >>").arg(label).arg(address); + + QRCodeDialog *d = new QRCodeDialog(title, address, label, tab == ReceivingTab, this); + d->show(); + } +#endif +}
--- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -54,6 +54,7 @@ void on_newAddressButton_clicked(); void on_copyToClipboard_clicked(); void selectionChanged(); + void on_showQRCode_clicked(); }; #endif // ADDRESSBOOKDIALOG_H
--- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -41,6 +41,7 @@ <qresource prefix="/images"> <file alias="about">res/images/about.png</file> <file alias="splash">res/images/splash2.jpg</file> + <file alias="qrcode">res/images/qrcode.png</file> </qresource> <qresource prefix="/movies"> <file alias="update_spinner">res/movies/update_spinner.mng</file>
--- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -80,6 +80,17 @@ </widget> </item> <item> + <widget class="QPushButton" name="showQRCode"> + <property name="text"> + <string>Show &QR Code</string> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/images/qrcode</normaloff>:/images/qrcode</iconset> + </property> + </widget> + </item> + <item> <widget class="QPushButton" name="deleteButton"> <property name="toolTip"> <string>Delete the currently selected address from the list. Only sending addresses can be deleted.</string>
new file mode 100644 --- /dev/null +++ b/src/qt/forms/qrcodedialog.ui @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QRCodeDialog</class> + <widget class="QDialog" name="QRCodeDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>320</width> + <height>404</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="lblQRCode"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>300</height> + </size> + </property> + <property name="text"> + <string>QR Code</string> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="widget" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="chkReq"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Request Payment</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="lblAm1"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Amount:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>lnReqAmount</cstring> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lnReqAmount"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>60</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblAm2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>BTC</string> + </property> + <property name="buddy"> + <cstring>lnReqAmount</cstring> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Label:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>lnLabel</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="lnLabel"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Message:</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="buddy"> + <cstring>lnMessage</cstring> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="lnMessage"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="btnSaveAs"> + <property name="text"> + <string>&Save As...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>chkReq</sender> + <signal>clicked(bool)</signal> + <receiver>lnReqAmount</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>92</x> + <y>285</y> + </hint> + <hint type="destinationlabel"> + <x>98</x> + <y>311</y> + </hint> + </hints> + </connection> + </connections> +</ui>
new file mode 100644 --- /dev/null +++ b/src/qt/qrcodedialog.cpp @@ -0,0 +1,106 @@ +#include "qrcodedialog.h" +#include "ui_qrcodedialog.h" +#include <QPixmap> +#include <QUrl> +#include <QFileDialog> +#include <QDesktopServices> +#include <QDebug> + +#include <qrencode.h> + +#define EXPORT_IMAGE_SIZE 256 + +QRCodeDialog::QRCodeDialog(const QString &title, const QString &addr, const QString &label, bool enableReq, QWidget *parent) : + QDialog(parent), + ui(new Ui::QRCodeDialog), + address(addr) +{ + ui->setupUi(this); + setWindowTitle(title); + setAttribute(Qt::WA_DeleteOnClose); + + ui->chkReq->setVisible(enableReq); + ui->lnReqAmount->setVisible(enableReq); + ui->lblAm1->setVisible(enableReq); + ui->lblAm2->setVisible(enableReq); + + ui->lnLabel->setText(label); + + genCode(); +} + +QRCodeDialog::~QRCodeDialog() +{ + delete ui; +} + +void QRCodeDialog::genCode() { + + QString uri = getURI(); + //qDebug() << "Encoding:" << uri.toUtf8().constData(); + QRcode *code = QRcode_encodeString(uri.toUtf8().constData(), 0, QR_ECLEVEL_L, QR_MODE_8, 1); + myImage = QImage(code->width + 8, code->width + 8, QImage::Format_RGB32); + myImage.fill(0xffffff); + unsigned char *p = code->data; + for(int y = 0; y < code->width; y++) { + for(int x = 0; x < code->width; x++) { + myImage.setPixel(x + 4, y + 4, ((*p & 1) ? 0x0 : 0xffffff)); + p++; + } + } + QRcode_free(code); + ui->lblQRCode->setPixmap(QPixmap::fromImage(myImage).scaled(300, 300)); +} + +QString QRCodeDialog::getURI() { + QString ret = QString("bitcoin:%1").arg(address); + + int paramCount = 0; + if(ui->chkReq->isChecked() && ui->lnReqAmount->text().isEmpty() == false) { + bool ok= false; + double amount = ui->lnReqAmount->text().toDouble(&ok); + if(ok) { + ret += QString("?amount=%1X8").arg(ui->lnReqAmount->text()); + paramCount++; + } + } + + if(ui->lnLabel->text().isEmpty() == false) { + QString lbl(QUrl::toPercentEncoding(ui->lnLabel->text())); + ret += QString("%1label=%2").arg(paramCount == 0 ? "?" : "&").arg(lbl); + paramCount++; + } + + if(ui->lnMessage->text().isEmpty() == false) { + QString msg(QUrl::toPercentEncoding(ui->lnMessage->text())); + ret += QString("%1message=%2").arg(paramCount == 0 ? "?" : "&").arg(msg); + paramCount++; + } + + return ret; +} + +void QRCodeDialog::on_lnReqAmount_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_lnLabel_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_lnMessage_textChanged(const QString &) { + genCode(); +} + +void QRCodeDialog::on_btnSaveAs_clicked() +{ + QString fn = QFileDialog::getSaveFileName(this, "Save Image...", QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation), "Images (*.png)"); + if(!fn.isEmpty()) { + myImage.scaled(EXPORT_IMAGE_SIZE, EXPORT_IMAGE_SIZE).save(fn); + } +} + +void QRCodeDialog::on_chkReq_toggled(bool) +{ + genCode(); +}
new file mode 100644 --- /dev/null +++ b/src/qt/qrcodedialog.h @@ -0,0 +1,37 @@ +#ifndef QRCODEDIALOG_H +#define QRCODEDIALOG_H + +#include <QDialog> +#include <QImage> + +namespace Ui { + class QRCodeDialog; +} + +class QRCodeDialog : public QDialog +{ + Q_OBJECT + +public: + explicit QRCodeDialog(const QString &title, const QString &address, const QString &label, bool allowReq, QWidget *parent = 0); + ~QRCodeDialog(); + +private slots: + void on_lnReqAmount_textChanged(const QString &arg1); + void on_lnLabel_textChanged(const QString &arg1); + void on_lnMessage_textChanged(const QString &arg1); + void on_btnSaveAs_clicked(); + + void on_chkReq_toggled(bool checked); + +private: + Ui::QRCodeDialog *ui; + QImage myImage; + + QString getURI(); + QString address; + + void genCode(); +}; + +#endif // QRCODEDIALOG_H
new file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c89a49bbceba56e1901487b970645dd9eea95608 GIT binary patch literal 5993 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 M8v5u8b-Snk1Hw;bEdT%j