Merge pull request #723 from TheBlueMatt/gitianfix
authorWladimir J. van der Laan <laanwj@gmail.com>
Fri, 23 Dec 2011 11:53:31 +0000 (03:53 -0800)
committerWladimir J. van der Laan <laanwj@gmail.com>
Fri, 23 Dec 2011 11:53:31 +0000 (03:53 -0800)
Fix #722.

22 files changed:
bitcoin-qt.pro
contrib/gitian-descriptors/gitian.yml
doc/build-osx.txt
doc/build-unix.txt
src/makefile.osx
src/qt/aboutdialog.cpp
src/qt/addressbookpage.cpp
src/qt/addressbookpage.h
src/qt/bitcoin.cpp
src/qt/bitcoin.qrc
src/qt/bitcoingui.cpp
src/qt/forms/addressbookpage.ui
src/qt/forms/qrcodedialog.ui [new file with mode: 0644]
src/qt/guiutil.cpp
src/qt/guiutil.h
src/qt/qrcodedialog.cpp [new file with mode: 0644]
src/qt/qrcodedialog.h [new file with mode: 0644]
src/qt/res/images/qrcode.png [new file with mode: 0644]
src/qt/transactiondesc.cpp
src/qt/transactiondesc.h
src/qt/transactionview.cpp
src/qt/transactionview.h

index 0bd2113..b690fae 100644 (file)
@@ -19,6 +19,14 @@ OBJECTS_DIR = build
 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 @@ FORMS += \
     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
index d0f8dec..903a9dc 100644 (file)
@@ -16,6 +16,7 @@ packages:
 - "libssl-dev"
 - "git-core"
 - "unzip"
+- "qrencode"
 reference_datetime: "2011-01-30 00:00:00"
 remotes:
 - "url": "https://github.com/bitcoin/bitcoin.git"
index 8002441..d47febe 100644 (file)
@@ -43,6 +43,9 @@ pushd bitcoin/contrib/minipupnpc; sudo port install; popd
 (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
index f4178ca..5389339 100644 (file)
@@ -23,12 +23,13 @@ the graphical bitcoin.
 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 @@ turned off by default.  Set USE_UPNP to a different value to control this:
  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 @@ Versions used in this release:
  Boost         1.37
  miniupnpc     1.6
 
-
 Dependency Build Instructions: Ubuntu & Debian
 ----------------------------------------------
 sudo apt-get install build-essential
index bd72e6b..cf5ec38 100644 (file)
@@ -96,6 +96,10 @@ else
 endif
 endif
 
+ifdef USE_QRCODE
+       DEFS += -DUSE_QRCODE=$(USE_QRCODE)
+       LIBS += -lqrencode
+endif
 
 all: bitcoind
 
index a3aa6de..08d7774 100644 (file)
@@ -7,7 +7,6 @@ AboutDialog::AboutDialog(QWidget *parent) :
     ui(new Ui::AboutDialog)
 {
     ui->setupUi(this);
-
 }
 
 void AboutDialog::setModel(ClientModel *model)
index 0a147c9..91412fc 100644 (file)
@@ -4,11 +4,17 @@
 #include "addresstablemodel.h"
 #include "editaddressdialog.h"
 #include "csvmodelwriter.h"
+#include "guiutil.h"
 
 #include <QSortFilterProxyModel>
 #include <QClipboard>
 #include <QFileDialog>
 #include <QMessageBox>
+#include <QMenu>
+
+#ifdef USE_QRCODE
+#include "qrcodedialog.h"
+#endif
 
 AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
     QDialog(parent),
@@ -25,6 +31,10 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
     ui->deleteButton->setIcon(QIcon());
 #endif
 
+#ifndef USE_QRCODE
+    ui->showQRCode->setVisible(false);
+#endif
+
     switch(mode)
     {
     case ForSending:
@@ -45,7 +55,28 @@ AddressBookPage::AddressBookPage(Mode mode, Tabs tab, QWidget *parent) :
         break;
     }
     ui->tableView->setTabKeyNavigation(false);
+    ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu);
+
+    // Context menu actions
+    QAction *copyAddressAction = new QAction(tr("Copy address"), this);
+    QAction *copyLabelAction = new QAction(tr("Copy label"), this);
+    QAction *editAction = new QAction(tr("Edit"), this);
+    deleteAction = new QAction(tr("Delete"), this);
 
+    contextMenu = new QMenu();
+    contextMenu->addAction(copyAddressAction);
+    contextMenu->addAction(copyLabelAction);
+    contextMenu->addAction(editAction);
+    contextMenu->addAction(deleteAction);
+
+    connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(on_copyToClipboard_clicked()));
+    connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(onCopyLabelAction()));
+    connect(editAction, SIGNAL(triggered()), this, SLOT(onEditAction()));
+    connect(deleteAction, SIGNAL(triggered()), this, SLOT(on_deleteButton_clicked()));
+
+    connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
+
+    // Pass through accept action from button box
     connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
 }
 
@@ -100,18 +131,29 @@ void AddressBookPage::setModel(AddressTableModel *model)
 
 void AddressBookPage::on_copyToClipboard_clicked()
 {
-    // Copy currently selected address to clipboard
-    //   (or nothing, if nothing selected)
-    QTableView *table = ui->tableView;
-    if(!table->selectionModel())
+    GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Address);
+}
+void AddressBookPage::onCopyLabelAction()
+{
+    GUIUtil::copyEntryData(ui->tableView, AddressTableModel::Label);
+}
+
+void AddressBookPage::onEditAction()
+{
+    if(!ui->tableView->selectionModel())
+        return;
+    QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows();
+    if(indexes.isEmpty())
         return;
-    QModelIndexList indexes = table->selectionModel()->selectedRows(AddressTableModel::Address);
 
-    foreach (QModelIndex index, indexes)
-    {
-        QVariant address = index.data();
-        QApplication::clipboard()->setText(address.toString());
-    }
+    EditAddressDialog dlg(
+            tab == SendingTab ?
+            EditAddressDialog::EditSendingAddress :
+            EditAddressDialog::EditReceivingAddress);
+    dlg.setModel(model);
+    QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0));
+    dlg.loadRow(origIndex.row());
+    dlg.exec();
 }
 
 void AddressBookPage::on_newAddressButton_clicked()
@@ -162,17 +204,23 @@ void AddressBookPage::selectionChanged()
         switch(tab)
         {
         case SendingTab:
+            // In sending tab, allow deletion of selection
             ui->deleteButton->setEnabled(true);
+            deleteAction->setEnabled(true);
             break;
         case ReceivingTab:
+            // Deleting receiving addresses, however, is not allowed
             ui->deleteButton->setEnabled(false);
+            deleteAction->setEnabled(false);
             break;
         }
         ui->copyToClipboard->setEnabled(true);
+        ui->showQRCode->setEnabled(true);
     }
     else
     {
         ui->deleteButton->setEnabled(false);
+        ui->showQRCode->setEnabled(false);
         ui->copyToClipboard->setEnabled(false);
     }
 }
@@ -197,6 +245,7 @@ void AddressBookPage::done(int retval)
 
     if(returnValue.isEmpty())
     {
+        // If no address entry selected, return rejected
         retval = Rejected;
     }
 
@@ -227,3 +276,32 @@ void AddressBookPage::exportClicked()
                               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
+}
+
+void AddressBookPage::contextualMenu(const QPoint &point)
+{
+    QModelIndex index = ui->tableView->indexAt(point);
+    if(index.isValid())
+    {
+        contextMenu->exec(QCursor::pos());
+    }
+}
index 1a97f3d..6b08f84 100644 (file)
@@ -12,6 +12,7 @@ QT_BEGIN_NAMESPACE
 class QTableView;
 class QItemSelection;
 class QSortFilterProxyModel;
+class QMenu;
 QT_END_NAMESPACE
 
 /** Widget that shows a list of sending or receiving addresses.
@@ -48,12 +49,23 @@ private:
     Tabs tab;
     QString returnValue;
     QSortFilterProxyModel *proxyModel;
+    QMenu *contextMenu;
+    QAction *deleteAction;
 
 private slots:
     void on_deleteButton_clicked();
     void on_newAddressButton_clicked();
+    /** Copy address of currently selected address entry to clipboard */
     void on_copyToClipboard_clicked();
     void selectionChanged();
+    void on_showQRCode_clicked();
+    /** Spawn contextual menu (right mouse menu) for address book entry */
+    void contextualMenu(const QPoint &point);
+
+    /** Copy label of currently selected address entry to clipboard */
+    void onCopyLabelAction();
+    /** Edit currently selected address entry */
+    void onEditAction();
 };
 
 #endif // ADDRESSBOOKDIALOG_H
index cba4e85..894bbb9 100644 (file)
@@ -114,6 +114,7 @@ std::string _(const char* psz)
 
 int main(int argc, char *argv[])
 {
+    // Internal string conversion is all UTF-8
     QTextCodec::setCodecForTr(QTextCodec::codecForName("UTF-8"));
     QTextCodec::setCodecForCStrings(QTextCodec::codecForTr());
 
index aea61d6..5693ae1 100644 (file)
@@ -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>
index 1c49683..d4ce8a7 100644 (file)
@@ -112,7 +112,6 @@ BitcoinGUI::BitcoinGUI(QWidget *parent):
 
     // Status bar notification icons
     QFrame *frameBlocks = new QFrame();
-    //frameBlocks->setFrameStyle(QFrame::Panel | QFrame::Sunken);
     frameBlocks->setContentsMargins(0,0,0,0);
     frameBlocks->setMinimumWidth(56);
     frameBlocks->setMaximumWidth(56);
index fb098c8..9b301cb 100644 (file)
       </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>
diff --git a/src/qt/forms/qrcodedialog.ui b/src/qt/forms/qrcodedialog.ui
new file mode 100644 (file)
index 0000000..fa21f60
--- /dev/null
@@ -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>
index 158b84a..22d9cc7 100644 (file)
 #include <QFont>
 #include <QLineEdit>
 #include <QUrl>
+#include <QTextDocument> // For Qt::escape
+#include <QAbstractItemView>
+#include <QApplication>
+#include <QClipboard>
 
 QString GUIUtil::dateTimeStr(qint64 nTime)
 {
@@ -72,3 +76,31 @@ bool GUIUtil::parseBitcoinURL(const QUrl *url, SendCoinsRecipient *out)
     }
     return true;
 }
+
+QString GUIUtil::HtmlEscape(const QString& str, bool fMultiLine)
+{
+    QString escaped = Qt::escape(str);
+    if(fMultiLine)
+    {
+        escaped = escaped.replace("\n", "<br>\n");
+    }
+    return escaped;
+}
+
+QString GUIUtil::HtmlEscape(const std::string& str, bool fMultiLine)
+{
+    return HtmlEscape(QString::fromStdString(str), fMultiLine);
+}
+
+void GUIUtil::copyEntryData(QAbstractItemView *view, int column, int role)
+{
+    if(!view || !view->selectionModel())
+        return;
+    QModelIndexList selection = view->selectionModel()->selectedRows(column);
+
+    if(!selection.isEmpty())
+    {
+        // Copy first item
+        QApplication::clipboard()->setText(selection.at(0).data(role).toString());
+    }
+}
index 94e3314..3a81bd2 100644 (file)
@@ -9,6 +9,7 @@ class QLineEdit;
 class QWidget;
 class QDateTime;
 class QUrl;
+class QAbstractItemView;
 QT_END_NAMESPACE
 class SendCoinsRecipient;
 
@@ -31,6 +32,19 @@ public:
     // Parse "bitcoin:" URL into recipient object, return true on succesful parsing
     // See Bitcoin URL definition discussion here: https://bitcointalk.org/index.php?topic=33490.0
     static bool parseBitcoinURL(const QUrl *url, SendCoinsRecipient *out);
+
+    // HTML escaping for rich text controls
+    static QString HtmlEscape(const QString& str, bool fMultiLine=false);
+    static QString HtmlEscape(const std::string& str, bool fMultiLine=false);
+
+    /** Copy a field of the currently selected entry of a view to the clipboard. Does nothing if nothing
+        is selected.
+       @param[in] column  Data column to extract from the model
+       @param[in] role    Data role to extract from the model
+       @see  TransactionView::copyLabel, TransactionView::copyAmount, TransactionView::copyAddress
+     */
+    static void copyEntryData(QAbstractItemView *view, int column, int role=Qt::EditRole);
+
 };
 
 #endif // GUIUTIL_H
diff --git a/src/qt/qrcodedialog.cpp b/src/qt/qrcodedialog.cpp
new file mode 100644 (file)
index 0000000..ed4c758
--- /dev/null
@@ -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();
+}
diff --git a/src/qt/qrcodedialog.h b/src/qt/qrcodedialog.h
new file mode 100644 (file)
index 0000000..7463a88
--- /dev/null
@@ -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
diff --git a/src/qt/res/images/qrcode.png b/src/qt/res/images/qrcode.png
new file mode 100644 (file)
index 0000000..c89a49b
Binary files /dev/null and b/src/qt/res/images/qrcode.png differ
index 821ef98..8de63c1 100644 (file)
@@ -7,25 +7,9 @@
 #include "qtui.h"
 
 #include <QString>
-#include <QTextDocument> // For Qt::escape
 
 using namespace std;
 
-QString TransactionDesc::HtmlEscape(const QString& str, bool fMultiLine)
-{
-    QString escaped = Qt::escape(str);
-    if(fMultiLine)
-    {
-        escaped = escaped.replace("\n", "<br>\n");
-    }
-    return escaped;
-}
-
-QString TransactionDesc::HtmlEscape(const std::string& str, bool fMultiLine)
-{
-    return HtmlEscape(QString::fromStdString(str), fMultiLine);
-}
-
 QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
 {
     if (!wtx.IsFinal())
@@ -86,7 +70,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
         {
             // Online transaction
             if (!wtx.mapValue["from"].empty())
-                strHTML += tr("<b>From:</b> ") + HtmlEscape(wtx.mapValue["from"]) + "<br>";
+                strHTML += tr("<b>From:</b> ") + GUIUtil::HtmlEscape(wtx.mapValue["from"]) + "<br>";
         }
         else
         {
@@ -105,9 +89,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
                             {
                                 strHTML += tr("<b>From:</b> ") + tr("unknown") + "<br>";
                                 strHTML += tr("<b>To:</b> ");
-                                strHTML += HtmlEscape(address.ToString());
+                                strHTML += GUIUtil::HtmlEscape(address.ToString());
                                 if (!wallet->mapAddressBook[address].empty())
-                                    strHTML += tr(" (yours, label: ") + HtmlEscape(wallet->mapAddressBook[address]) + ")";
+                                    strHTML += tr(" (yours, label: ") + GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + ")";
                                 else
                                     strHTML += tr(" (yours)");
                                 strHTML += "<br>";
@@ -129,8 +113,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
             strAddress = wtx.mapValue["to"];
             strHTML += tr("<b>To:</b> ");
             if (wallet->mapAddressBook.count(strAddress) && !wallet->mapAddressBook[strAddress].empty())
-                strHTML += HtmlEscape(wallet->mapAddressBook[strAddress]) + " ";
-            strHTML += HtmlEscape(strAddress) + "<br>";
+                strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[strAddress]) + " ";
+            strHTML += GUIUtil::HtmlEscape(strAddress) + "<br>";
         }
 
         //
@@ -188,8 +172,8 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
                         {
                             strHTML += tr("<b>To:</b> ");
                             if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty())
-                                strHTML += HtmlEscape(wallet->mapAddressBook[address]) + " ";
-                            strHTML += HtmlEscape(address.ToString());
+                                strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " ";
+                            strHTML += GUIUtil::HtmlEscape(address.ToString());
                             strHTML += "<br>";
                         }
                     }
@@ -230,9 +214,9 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
         // Message
         //
         if (!wtx.mapValue["message"].empty())
-            strHTML += QString("<br><b>") + tr("Message:") + "</b><br>" + HtmlEscape(wtx.mapValue["message"], true) + "<br>";
+            strHTML += QString("<br><b>") + tr("Message:") + "</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["message"], true) + "<br>";
         if (!wtx.mapValue["comment"].empty())
-            strHTML += QString("<br><b>") + tr("Comment:") + "</b><br>" + HtmlEscape(wtx.mapValue["comment"], true) + "<br>";
+            strHTML += QString("<br><b>") + tr("Comment:") + "</b><br>" + GUIUtil::HtmlEscape(wtx.mapValue["comment"], true) + "<br>";
 
         if (wtx.IsCoinBase())
             strHTML += QString("<br>") + tr("Generated coins must wait 120 blocks before they can be spent.  When you generated this block, it was broadcast to the network to be added to the block chain.  If it fails to get into the chain, it will change to \"not accepted\" and not be spendable.  This may occasionally happen if another node generates a block within a few seconds of yours.") + "<br>";
@@ -251,7 +235,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
                     strHTML += "<b>Credit:</b> " + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,wallet->GetCredit(txout)) + "<br>";
 
             strHTML += "<br><b>Transaction:</b><br>";
-            strHTML += HtmlEscape(wtx.ToString(), true);
+            strHTML += GUIUtil::HtmlEscape(wtx.ToString(), true);
 
             CTxDB txdb("r"); // To fetch source txouts
 
@@ -274,7 +258,7 @@ QString TransactionDesc::toHTML(CWallet *wallet, CWalletTx &wtx)
                             if (ExtractAddress(vout.scriptPubKey, address))
                             {
                                 if (wallet->mapAddressBook.count(address) && !wallet->mapAddressBook[address].empty())
-                                    strHTML += HtmlEscape(wallet->mapAddressBook[address]) + " ";
+                                    strHTML += GUIUtil::HtmlEscape(wallet->mapAddressBook[address]) + " ";
                                 strHTML += QString::fromStdString(address.ToString());
                             }
                             strHTML = strHTML + " Amount=" + BitcoinUnits::formatWithUnit(BitcoinUnits::BTC,vout.nValue);
index 55b9eaf..2523f9a 100644 (file)
@@ -18,8 +18,6 @@ public:
 private:
     TransactionDesc() {}
 
-    static QString HtmlEscape(const QString& str, bool fMultiLine=false);
-    static QString HtmlEscape(const std::string &str, bool fMultiLine=false);
     static QString FormatTxStatus(const CWalletTx& wtx);
 };
 
index 2dcbf1e..cba0629 100644 (file)
@@ -10,6 +10,7 @@
 #include "transactiondescdialog.h"
 #include "editaddressdialog.h"
 #include "optionsmodel.h"
+#include "guiutil.h"
 
 #include <QScrollBar>
 #include <QComboBox>
@@ -100,7 +101,6 @@ TransactionView::TransactionView(QWidget *parent) :
     QVBoxLayout *vlayout = new QVBoxLayout(this);
     vlayout->setContentsMargins(0,0,0,0);
     vlayout->setSpacing(0);
-    //vlayout->addLayout(hlayout2);
 
     QTableView *view = new QTableView(this);
     vlayout->addLayout(hlayout);
@@ -124,12 +124,14 @@ TransactionView::TransactionView(QWidget *parent) :
     // Actions
     QAction *copyAddressAction = new QAction(tr("Copy address"), this);
     QAction *copyLabelAction = new QAction(tr("Copy label"), this);
+    QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
     QAction *editLabelAction = new QAction(tr("Edit label"), this);
     QAction *showDetailsAction = new QAction(tr("Show details..."), this);
 
     contextMenu = new QMenu();
     contextMenu->addAction(copyAddressAction);
     contextMenu->addAction(copyLabelAction);
+    contextMenu->addAction(copyAmountAction);
     contextMenu->addAction(editLabelAction);
     contextMenu->addAction(showDetailsAction);
 
@@ -140,14 +142,11 @@ TransactionView::TransactionView(QWidget *parent) :
     connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));
 
     connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
-
-    connect(view,
-            SIGNAL(customContextMenuRequested(QPoint)),
-            this,
-            SLOT(contextualMenu(QPoint)));
+    connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));
 
     connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
     connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
+    connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
     connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
     connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
 }
@@ -303,24 +302,17 @@ void TransactionView::contextualMenu(const QPoint &point)
 
 void TransactionView::copyAddress()
 {
-    if(!transactionView->selectionModel())
-        return;
-    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
-    if(!selection.isEmpty())
-    {
-        QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::AddressRole).toString());
-    }
+    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
 }
 
 void TransactionView::copyLabel()
 {
-    if(!transactionView->selectionModel())
-        return;
-    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
-    if(!selection.isEmpty())
-    {
-        QApplication::clipboard()->setText(selection.at(0).data(TransactionTableModel::LabelRole).toString());
-    }
+    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
+}
+
+void TransactionView::copyAmount()
+{
+    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
 }
 
 void TransactionView::editLabel()
index 67d0b46..bc6e1e4 100644 (file)
@@ -60,6 +60,11 @@ private:
 private slots:
     void contextualMenu(const QPoint &);
     void dateRangeChanged();
+    void showDetails();
+    void copyAddress();
+    void editLabel();
+    void copyLabel();
+    void copyAmount();
 
 signals:
     void doubleClicked(const QModelIndex&);
@@ -70,10 +75,6 @@ public slots:
     void changedPrefix(const QString &prefix);
     void changedAmount(const QString &amount);
     void exportClicked();
-    void showDetails();
-    void copyAddress();
-    void editLabel();
-    void copyLabel();
 
 };