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 &amp;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>&amp;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