diff options
55 files changed, 2707 insertions, 204 deletions
diff --git a/contrib/README.md b/contrib/README.md index eb0d3ee2c9..d1cdd7eb38 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -13,8 +13,8 @@ This is a 'getwork' CPU mining client for Bitcoin. It is pure-python, and theref Use the raw transactions API to send coins received on a particular address (or addresses). -### [Wallet Tools](/contrib/wallettools) ### -Contains a wallet change password and unlock script. Used to prevent users from having to enter their password as a command-line argument. +### WalletTools +Removed. Please see [/contrib/bitrpc](/contrib/bitrpc). Repository Tools --------------------- diff --git a/contrib/bitrpc/README.md b/contrib/bitrpc/README.md index 2dde60a08e..f5ef2f0405 100644 --- a/contrib/bitrpc/README.md +++ b/contrib/bitrpc/README.md @@ -1,2 +1,8 @@ -### BitRPC ### -Allows for sending of all standard Bitcoin commands via RPC rather than as command line args.
\ No newline at end of file +### BitRPC +Allows for sending of all standard Bitcoin commands via RPC rather than as command line args. + +### Looking for Wallet Tools? +BitRPC.py is able to do the exact same thing as `walletchangepass.py` and `walletunlock.py`. Their respective commands in BitRPC.py are: + + bitrpc.py walletpassphrasechange + bitrpc.py walletpassphrase
\ No newline at end of file diff --git a/contrib/wallettools/README.md b/contrib/wallettools/README.md deleted file mode 100644 index 3a71ba1436..0000000000 --- a/contrib/wallettools/README.md +++ /dev/null @@ -1,4 +0,0 @@ -### Wallet Tools ### -These are two simple python scripts which send the appropriate RPC commands to unlock a wallet and change a wallet password. **They are intended to prevent users from having to enter their password as a command-line argument which could then be stored in the console buffer/history in plaintext.** - -Both tools rely on bitcoin/bitcoind running with `server=1` and an `rpcuser` and `rpcpassword` set in `bitcoin.conf`. They can be easily modified for non-standard ports. [walletunlock.py](/contrib/wallettools/walletunlock.py) unlocks the wallet for 60 seconds by default, changeable in code, and both modules rely upon python-json-rpc. diff --git a/contrib/wallettools/walletchangepass.py b/contrib/wallettools/walletchangepass.py deleted file mode 100644 index 30f3f5b26a..0000000000 --- a/contrib/wallettools/walletchangepass.py +++ /dev/null @@ -1,5 +0,0 @@ -from jsonrpc import ServiceProxy -access = ServiceProxy("http://127.0.0.1:8332") -pwd = raw_input("Enter old wallet passphrase: ") -pwd2 = raw_input("Enter new wallet passphrase: ") -access.walletpassphrasechange(pwd, pwd2)
\ No newline at end of file diff --git a/contrib/wallettools/walletunlock.py b/contrib/wallettools/walletunlock.py deleted file mode 100644 index f847c6fe61..0000000000 --- a/contrib/wallettools/walletunlock.py +++ /dev/null @@ -1,4 +0,0 @@ -from jsonrpc import ServiceProxy -access = ServiceProxy("http://127.0.0.1:8332") -pwd = raw_input("Enter wallet passphrase: ") -access.walletpassphrase(pwd, 60)
\ No newline at end of file diff --git a/doc/Doxyfile b/doc/Doxyfile index c32d0f8959..9bad2d0759 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -34,7 +34,7 @@ PROJECT_NAME = Bitcoin # This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = 0.5.0 +PROJECT_NUMBER = 0.9.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer diff --git a/doc/release-process.md b/doc/release-process.md index 9e0b860a8c..feadb3c1b0 100644 --- a/doc/release-process.md +++ b/doc/release-process.md @@ -121,6 +121,8 @@ repackage gitian builds for release as stand-alone zip/tar/installer exe * update bitcoin.org version make sure all OS download links go to the right versions + +* update download sizes on bitcoin.org/_templates/download.html * update forum version diff --git a/src/Makefile.am b/src/Makefile.am index 24a95eed84..7450507b34 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,7 @@ DIST_SUBDIRS = . qt test # bitcoin core # BITCOIN_CORE_H = addrman.h alert.h allocators.h base58.h bignum.h \ bitcoinrpc.h bloom.h chainparams.h checkpoints.h checkqueue.h \ - clientversion.h compat.h core.h coins.h crypter.h db.h hash.h init.h \ + clientversion.h coincontrol.h compat.h core.h coins.h crypter.h db.h hash.h init.h \ key.h keystore.h leveldbwrapper.h limitedmap.h main.h miner.h mruset.h \ netbase.h net.h noui.h protocol.h script.h serialize.h sync.h threadsafety.h \ txdb.h txmempool.h ui_interface.h uint256.h util.h version.h walletdb.h wallet.h diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index f0aad4f33a..6dbab240bf 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -13,6 +13,22 @@ #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> +/* Introduction text for doxygen: */ + +/*! \mainpage Developer documentation + * + * \section intro_sec Introduction + * + * This is the developer documentation of the reference client for an experimental new digital currency called Bitcoin (http://www.bitcoin.org/), + * which enables instant payments to anyone, anywhere in the world. Bitcoin uses peer-to-peer technology to operate + * with no central authority: managing transactions and issuing money are carried out collectively by the network. + * + * The software is a community-driven open source project, released under the MIT license. + * + * \section Navigation + * Use the buttons <code>Namespaces</code>, <code>Classes</code> or <code>Files</code> at the top of the page to start navigating the code. + */ + void DetectShutdownThread(boost::thread_group* threadGroup) { bool fShutdown = ShutdownRequested(); diff --git a/src/coincontrol.h b/src/coincontrol.h new file mode 100644 index 0000000000..97c30c2713 --- /dev/null +++ b/src/coincontrol.h @@ -0,0 +1,63 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef COINCONTROL_H +#define COINCONTROL_H + +#include "core.h" + +/** Coin Control Features. */ +class CCoinControl +{ +public: + CTxDestination destChange; + + CCoinControl() + { + SetNull(); + } + + void SetNull() + { + destChange = CNoDestination(); + setSelected.clear(); + } + + bool HasSelected() const + { + return (setSelected.size() > 0); + } + + bool IsSelected(const uint256& hash, unsigned int n) const + { + COutPoint outpt(hash, n); + return (setSelected.count(outpt) > 0); + } + + void Select(COutPoint& output) + { + setSelected.insert(output); + } + + void UnSelect(COutPoint& output) + { + setSelected.erase(output); + } + + void UnSelectAll() + { + setSelected.clear(); + } + + void ListSelected(std::vector<COutPoint>& vOutpoints) + { + vOutpoints.assign(setSelected.begin(), setSelected.end()); + } + +private: + std::set<COutPoint> setSelected; + +}; + +#endif // COINCONTROL_H diff --git a/src/m4/ax_boost_base.m4 b/src/m4/ax_boost_base.m4 index 54a2a1bee7..57d14fe48d 100644 --- a/src/m4/ax_boost_base.m4 +++ b/src/m4/ax_boost_base.m4 @@ -33,7 +33,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 20 +#serial 21 AC_DEFUN([AX_BOOST_BASE], [ @@ -91,9 +91,11 @@ if test "x$want_boost" = "xyes"; then dnl are found, e.g. when only header-only libraries are installed! libsubdirs="lib" ax_arch=`uname -m` - if test $ax_arch = x86_64 -o $ax_arch = ppc64 -o $ax_arch = s390x -o $ax_arch = sparc64; then + case $ax_arch in + x86_64|ppc64|s390x|sparc64|aarch64) libsubdirs="lib64 lib lib64" - fi + ;; + esac dnl first we check the system location for boost libraries dnl this location ist chosen if boost libraries are installed with the --layout=system option diff --git a/src/m4/ax_boost_filesystem.m4 b/src/m4/ax_boost_filesystem.m4 index 2a62da8d89..f162163cdc 100644 --- a/src/m4/ax_boost_filesystem.m4 +++ b/src/m4/ax_boost_filesystem.m4 @@ -31,7 +31,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 21 +#serial 26 AC_DEFUN([AX_BOOST_FILESYSTEM], [ @@ -81,14 +81,14 @@ AC_DEFUN([AX_BOOST_FILESYSTEM], AC_DEFINE(HAVE_BOOST_FILESYSTEM,,[define if the Boost::Filesystem library is available]) BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` if test "x$ax_boost_user_filesystem_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_filesystem*.so* $BOOSTLIBDIR/libboost_filesystem*.dylib* $BOOSTLIBDIR/libboost_filesystem*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_filesystem.*\)\.so.*$;\1;' -e 's;^lib\(boost_filesystem.*\)\.a*$;\1;' -e 's;^lib\(boost_filesystem.*\)\.dylib$;\1;'` ; do + for libextension in `ls -r $BOOSTLIBDIR/libboost_filesystem* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], [link_filesystem="no"]) done - if test "x$link_program_options" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_filesystem*.{dll,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_filesystem.*\)\.dll.*$;\1;' -e 's;^\(boost_filesystem.*\)\.a*$;\1;'` ; do + if test "x$link_filesystem" != "xyes"; then + for libextension in `ls -r $BOOSTLIBDIR/boost_filesystem* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_FILESYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes"; break], diff --git a/src/m4/ax_boost_program_options.m4 b/src/m4/ax_boost_program_options.m4 index d612f91da3..65a39c8c70 100644 --- a/src/m4/ax_boost_program_options.m4 +++ b/src/m4/ax_boost_program_options.m4 @@ -29,7 +29,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 20 +#serial 22 AC_DEFUN([AX_BOOST_PROGRAM_OPTIONS], [ @@ -74,14 +74,14 @@ AC_DEFUN([AX_BOOST_PROGRAM_OPTIONS], AC_DEFINE(HAVE_BOOST_PROGRAM_OPTIONS,,[define if the Boost::PROGRAM_OPTIONS library is available]) BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` if test "x$ax_boost_user_program_options_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_program_options*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.a*$;\1;'` ; do + for libextension in `ls $BOOSTLIBDIR/libboost_program_options*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.dylib* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.dylib.*$;\1;'` `ls $BOOSTLIBDIR/libboost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_program_options.*\)\.a.*$;\1;'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break], [link_program_options="no"]) done if test "x$link_program_options" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_program_options*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.a*$;\1;'` ; do + for libextension in `ls $BOOSTLIBDIR/boost_program_options*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_program_options*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_program_options.*\)\.a.*$;\1;'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib"; AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes"; break], diff --git a/src/m4/ax_boost_system.m4 b/src/m4/ax_boost_system.m4 index 7fbf6d360d..c4c45559d8 100644 --- a/src/m4/ax_boost_system.m4 +++ b/src/m4/ax_boost_system.m4 @@ -31,7 +31,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 14 +#serial 17 AC_DEFUN([AX_BOOST_SYSTEM], [ @@ -83,14 +83,14 @@ AC_DEFUN([AX_BOOST_SYSTEM], LDFLAGS_SAVE=$LDFLAGS if test "x$ax_boost_user_system_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_system*.so* $BOOSTLIBDIR/libboost_system*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_system.*\)\.so.*$;\1;' -e 's;^lib\(boost_system.*\)\.a*$;\1;'` ; do + for libextension in `ls -r $BOOSTLIBDIR/libboost_system* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], [link_system="no"]) done if test "x$link_system" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_system*.{dll,a}* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_system.*\)\.dll.*$;\1;' -e 's;^\(boost_system.*\)\.a*$;\1;'` ; do + for libextension in `ls -r $BOOSTLIBDIR/boost_system* 2>/dev/null | sed 's,.*/,,' | sed -e 's,\..*,,'` ; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_SYSTEM_LIB="-l$ax_lib"; AC_SUBST(BOOST_SYSTEM_LIB) link_system="yes"; break], diff --git a/src/m4/ax_boost_thread.m4 b/src/m4/ax_boost_thread.m4 index d9cd8a1d1d..79e12cdb4e 100644 --- a/src/m4/ax_boost_thread.m4 +++ b/src/m4/ax_boost_thread.m4 @@ -30,7 +30,7 @@ # and this notice are preserved. This file is offered as-is, without any # warranty. -#serial 22 +#serial 27 AC_DEFUN([AX_BOOST_THREAD], [ @@ -68,17 +68,13 @@ AC_DEFUN([AX_BOOST_THREAD], [AC_LANG_PUSH([C++]) CXXFLAGS_SAVE=$CXXFLAGS - # let us handle platform dependent issues in - # configure.ac - - # if test "x$build_os" = "xsolaris" ; then - # CXXFLAGS="-pthreads $CXXFLAGS" - # elif test "x$build_os" = "xming32" ; then - # CXXFLAGS="-mthreads $CXXFLAGS" - # else - # CXXFLAGS="-pthread $CXXFLAGS" - # fi - + if test "x$host_os" = "xsolaris" ; then + CXXFLAGS="-pthreads $CXXFLAGS" + elif test "x$host_os" = "xmingw32" ; then + CXXFLAGS="-mthreads $CXXFLAGS" + else + CXXFLAGS="-pthread $CXXFLAGS" + fi AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/thread/thread.hpp>]], [[boost::thread_group thrds; return 0;]])], @@ -87,13 +83,13 @@ AC_DEFUN([AX_BOOST_THREAD], AC_LANG_POP([C++]) ]) if test "x$ax_cv_boost_thread" = "xyes"; then - # if test "x$build_os" = "xsolaris" ; then - # BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS" - # elif test "x$build_os" = "xming32" ; then - # BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS" - # else - # BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS" - # fi + if test "x$host_os" = "xsolaris" ; then + BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS" + elif test "x$host_os" = "xmingw32" ; then + BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS" + else + BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS" + fi AC_SUBST(BOOST_CPPFLAGS) @@ -101,21 +97,21 @@ AC_DEFUN([AX_BOOST_THREAD], BOOSTLIBDIR=`echo $BOOST_LDFLAGS | sed -e 's/@<:@^\/@:>@*//'` LDFLAGS_SAVE=$LDFLAGS - # case "x$build_os" in - # *bsd* ) - # LDFLAGS="-pthread $LDFLAGS" - # break; - # ;; - # esac + case "x$host_os" in + *bsd* ) + LDFLAGS="-pthread $LDFLAGS" + break; + ;; + esac if test "x$ax_boost_user_thread_lib" = "x"; then - for libextension in `ls $BOOSTLIBDIR/libboost_thread*.so* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_thread.*\)\.so.*$;\1;'` `ls $BOOSTLIBDIR/libboost_thread*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^lib\(boost_thread.*\)\.a*$;\1;'`; do + for libextension in `ls -r $BOOSTLIBDIR/libboost_thread* 2>/dev/null | sed 's,.*/lib,,' | sed 's,\..*,,'`; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break], [link_thread="no"]) done if test "x$link_thread" != "xyes"; then - for libextension in `ls $BOOSTLIBDIR/boost_thread*.dll* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_thread.*\)\.dll.*$;\1;'` `ls $BOOSTLIBDIR/boost_thread*.a* 2>/dev/null | sed 's,.*/,,' | sed -e 's;^\(boost_thread.*\)\.a*$;\1;'` ; do + for libextension in `ls -r $BOOSTLIBDIR/boost_thread* 2>/dev/null | sed 's,.*/,,' | sed 's,\..*,,'`; do ax_lib=${libextension} AC_CHECK_LIB($ax_lib, exit, [BOOST_THREAD_LIB="-l$ax_lib"; AC_SUBST(BOOST_THREAD_LIB) link_thread="yes"; break], @@ -134,17 +130,17 @@ AC_DEFUN([AX_BOOST_THREAD], if test "x$ax_lib" = "x"; then AC_MSG_ERROR(Could not find a version of the library!) fi - # if test "x$link_thread" = "xno"; then - # AC_MSG_ERROR(Could not link against $ax_lib !) - # else - # case "x$build_os" in - # *bsd* ) - # BOOST_LDFLAGS="-pthread $BOOST_LDFLAGS" - # break; - # ;; - # esac + if test "x$link_thread" = "xno"; then + AC_MSG_ERROR(Could not link against $ax_lib !) + else + case "x$host_os" in + *bsd* ) + BOOST_LDFLAGS="-pthread $BOOST_LDFLAGS" + break; + ;; + esac - # fi + fi fi CPPFLAGS="$CPPFLAGS_SAVED" diff --git a/src/m4/ax_pthread.m4 b/src/m4/ax_pthread.m4 index 6d400ed4e8..d383ad5c6d 100644 --- a/src/m4/ax_pthread.m4 +++ b/src/m4/ax_pthread.m4 @@ -82,7 +82,7 @@ # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. -#serial 20 +#serial 21 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ @@ -103,8 +103,8 @@ if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then save_LIBS="$LIBS" LIBS="$PTHREAD_LIBS $LIBS" AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) - AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) - AC_MSG_RESULT($ax_pthread_ok) + AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) if test x"$ax_pthread_ok" = xno; then PTHREAD_LIBS="" PTHREAD_CFLAGS="" @@ -164,6 +164,20 @@ case ${host_os} in ;; esac +# Clang doesn't consider unrecognized options an error unless we specify +# -Werror. We throw in some extra Clang-specific options to ensure that +# this doesn't happen for GCC, which also accepts -Werror. + +AC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags]) +save_CFLAGS="$CFLAGS" +ax_pthread_extra_flags="-Werror" +CFLAGS="$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument" +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])], + [AC_MSG_RESULT([yes])], + [ax_pthread_extra_flags= + AC_MSG_RESULT([no])]) +CFLAGS="$save_CFLAGS" + if test x"$ax_pthread_ok" = xno; then for flag in $ax_pthread_flags; do @@ -178,7 +192,7 @@ for flag in $ax_pthread_flags; do ;; pthread-config) - AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) if test x"$ax_pthread_config" = xno; then continue; fi PTHREAD_CFLAGS="`pthread-config --cflags`" PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" @@ -193,7 +207,7 @@ for flag in $ax_pthread_flags; do save_LIBS="$LIBS" save_CFLAGS="$CFLAGS" LIBS="$PTHREAD_LIBS $LIBS" - CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags" # Check for various functions. We must include pthread.h, # since some functions may be macros. (On the Sequent, we @@ -219,7 +233,7 @@ for flag in $ax_pthread_flags; do LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" - AC_MSG_RESULT($ax_pthread_ok) + AC_MSG_RESULT([$ax_pthread_ok]) if test "x$ax_pthread_ok" = xyes; then break; fi @@ -245,9 +259,9 @@ if test "x$ax_pthread_ok" = xyes; then [attr_name=$attr; break], []) done - AC_MSG_RESULT($attr_name) + AC_MSG_RESULT([$attr_name]) if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then - AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name], [Define to necessary symbol if this constant uses a non-standard name on your system.]) fi @@ -261,24 +275,25 @@ if test "x$ax_pthread_ok" = xyes; then if test "$GCC" = "yes"; then flag="-D_REENTRANT" else + # TODO: What about Clang on Solaris? flag="-mt -D_REENTRANT" fi ;; esac - AC_MSG_RESULT(${flag}) + AC_MSG_RESULT([$flag]) if test "x$flag" != xno; then PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" fi AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], - ax_cv_PTHREAD_PRIO_INHERIT, [ - AC_LINK_IFELSE([ - AC_LANG_PROGRAM([[#include <pthread.h>]], [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT], [ + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], + [[int i = PTHREAD_PRIO_INHERIT;]])], [ax_cv_PTHREAD_PRIO_INHERIT=yes], [ax_cv_PTHREAD_PRIO_INHERIT=no]) ]) AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], - AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])]) LIBS="$save_LIBS" CFLAGS="$save_CFLAGS" @@ -301,13 +316,13 @@ fi test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" -AC_SUBST(PTHREAD_LIBS) -AC_SUBST(PTHREAD_CFLAGS) -AC_SUBST(PTHREAD_CC) +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: if test x"$ax_pthread_ok" = xyes; then - ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) : else ax_pthread_ok=no diff --git a/src/main.cpp b/src/main.cpp index 5088de69a1..c4d58f970d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -598,12 +598,11 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) return true; } -int64_t GetMinFee(const CTransaction& tx, bool fAllowFree, enum GetMinFee_mode mode) +int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode) { // Base fee is either nMinTxFee or nMinRelayTxFee int64_t nBaseFee = (mode == GMF_RELAY) ? tx.nMinRelayTxFee : tx.nMinTxFee; - unsigned int nBytes = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); int64_t nMinFee = (1 + (int64_t)nBytes / 1000) * nBaseFee; if (fAllowFree) @@ -739,7 +738,7 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa unsigned int nSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); // Don't accept it if it can't get into a block - int64_t txMinFee = GetMinFee(tx, true, GMF_RELAY); + int64_t txMinFee = GetMinFee(tx, nSize, true, GMF_RELAY); if (fLimitFree && nFees < txMinFee) return state.DoS(0, error("AcceptToMemoryPool : not enough fees %s, %"PRId64" < %"PRId64, hash.ToString().c_str(), nFees, txMinFee), diff --git a/src/main.h b/src/main.h index b02aa60665..cf803ae25e 100644 --- a/src/main.h +++ b/src/main.h @@ -258,7 +258,7 @@ enum GetMinFee_mode GMF_SEND, }; -int64_t GetMinFee(const CTransaction& tx, bool fAllowFree, enum GetMinFee_mode mode); +int64_t GetMinFee(const CTransaction& tx, unsigned int nBytes, bool fAllowFree, enum GetMinFee_mode mode); // // Check transaction inputs, and make sure any diff --git a/src/net.cpp b/src/net.cpp index 637f98d782..c547cf3337 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -623,7 +623,7 @@ void CNode::copyStats(CNodeStats &stats) X(nSendBytes); X(nRecvBytes); stats.fSyncNode = (this == pnodeSync); - + // It is common for nodes with good ping times to suddenly become lagged, // due to a new block arriving or other large transfer. // Merely reporting pingtime might fool the caller into thinking the node was still responsive, @@ -634,11 +634,11 @@ void CNode::copyStats(CNodeStats &stats) if ((0 != nPingNonceSent) && (0 != nPingUsecStart)) { nPingUsecWait = GetTimeMicros() - nPingUsecStart; } - + // Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :) stats.dPingTime = (((double)nPingUsecTime) / 1e6); stats.dPingWait = (((double)nPingUsecWait) / 1e6); - + // Leave string empty if addrLocal invalid (not filled in yet) stats.addrLocal = addrLocal.IsValid() ? addrLocal.ToString() : ""; } @@ -1539,9 +1539,9 @@ void ThreadMessageHandler() CNode* pnodeTrickle = NULL; if (!vNodesCopy.empty()) pnodeTrickle = vNodesCopy[GetRand(vNodesCopy.size())]; - + bool fSleep = true; - + BOOST_FOREACH(CNode* pnode, vNodesCopy) { if (pnode->fDisconnect) @@ -1554,7 +1554,7 @@ void ThreadMessageHandler() { if (!g_signals.ProcessMessages(pnode)) pnode->CloseSocketDisconnect(); - + if (pnode->nSendSize < SendBufferSize()) { if (!pnode->vRecvGetData.empty() || (!pnode->vRecvMsg.empty() && pnode->vRecvMsg[0].complete())) @@ -1580,7 +1580,7 @@ void ThreadMessageHandler() BOOST_FOREACH(CNode* pnode, vNodesCopy) pnode->Release(); } - + if (fSleep) MilliSleep(100); } diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index e05d3d79af..31c032ecbf 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -72,7 +72,10 @@ QT_TS = locale/bitcoin_ach.ts \ locale/bitcoin_zh_TW.ts QT_FORMS_UI = forms/aboutdialog.ui forms/addressbookpage.ui \ - forms/askpassphrasedialog.ui forms/editaddressdialog.ui forms/intro.ui \ + forms/askpassphrasedialog.ui \ + forms/coincontroldialog.ui \ + forms/editaddressdialog.ui \ + forms/intro.ui \ forms/openuridialog.ui \ forms/optionsdialog.ui forms/overviewpage.ui forms/receiverequestdialog.ui \ forms/receivecoinsdialog.ui \ @@ -83,6 +86,8 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ moc_addresstablemodel.cpp moc_askpassphrasedialog.cpp \ moc_bitcoinaddressvalidator.cpp moc_bitcoinamountfield.cpp \ moc_bitcoingui.cpp moc_bitcoinunits.cpp moc_clientmodel.cpp \ + moc_coincontroldialog.cpp \ + moc_coincontroltreewidget.cpp \ moc_csvmodelwriter.cpp moc_editaddressdialog.cpp moc_guiutil.cpp \ moc_intro.cpp moc_macdockiconhandler.cpp moc_macnotificationhandler.cpp \ moc_monitoreddatamapper.cpp moc_notificator.cpp \ @@ -110,7 +115,7 @@ PROTOBUF_PROTO = paymentrequest.proto BITCOIN_QT_H = aboutdialog.h addressbookpage.h addresstablemodel.h \ askpassphrasedialog.h bitcoinaddressvalidator.h bitcoinamountfield.h \ - bitcoingui.h bitcoinunits.h clientmodel.h csvmodelwriter.h \ + bitcoingui.h bitcoinunits.h clientmodel.h coincontroldialog.h coincontroltreewidget.h csvmodelwriter.h \ editaddressdialog.h guiconstants.h guiutil.h intro.h macdockiconhandler.h \ macnotificationhandler.h monitoreddatamapper.h notificator.h \ openuridialog.h \ @@ -143,7 +148,10 @@ RES_ICONS = res/icons/bitcoin.png res/icons/address-book.png \ BITCOIN_QT_CPP = aboutdialog.cpp addressbookpage.cpp \ addresstablemodel.cpp askpassphrasedialog.cpp bitcoinaddressvalidator.cpp \ bitcoinamountfield.cpp bitcoin.cpp bitcoingui.cpp \ - bitcoinunits.cpp clientmodel.cpp csvmodelwriter.cpp editaddressdialog.cpp \ + bitcoinunits.cpp clientmodel.cpp \ + coincontroldialog.cpp \ + coincontroltreewidget.cpp \ + csvmodelwriter.cpp editaddressdialog.cpp \ guiutil.cpp intro.cpp monitoreddatamapper.cpp notificator.cpp \ openuridialog.cpp \ optionsdialog.cpp optionsmodel.cpp overviewpage.cpp paymentrequestplus.cpp \ diff --git a/src/qt/aboutdialog.h b/src/qt/aboutdialog.h index b02be74844..1b131c4dcc 100644 --- a/src/qt/aboutdialog.h +++ b/src/qt/aboutdialog.h @@ -19,7 +19,7 @@ class AboutDialog : public QDialog Q_OBJECT public: - explicit AboutDialog(QWidget *parent = 0); + explicit AboutDialog(QWidget *parent); ~AboutDialog(); void setModel(ClientModel *model); diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index cc3afb2655..abda6c7981 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -158,6 +158,9 @@ void AddressBookPage::onCopyLabelAction() void AddressBookPage::onEditAction() { + if(!model) + return; + if(!ui->tableView->selectionModel()) return; QModelIndexList indexes = ui->tableView->selectionModel()->selectedRows(); @@ -165,9 +168,9 @@ void AddressBookPage::onEditAction() return; EditAddressDialog dlg( - tab == SendingTab ? - EditAddressDialog::EditSendingAddress : - EditAddressDialog::EditReceivingAddress); + tab == SendingTab ? + EditAddressDialog::EditSendingAddress : + EditAddressDialog::EditReceivingAddress, this); dlg.setModel(model); QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0)); dlg.loadRow(origIndex.row()); @@ -180,9 +183,9 @@ void AddressBookPage::on_newAddress_clicked() return; EditAddressDialog dlg( - tab == SendingTab ? - EditAddressDialog::NewSendingAddress : - EditAddressDialog::NewReceivingAddress, this); + tab == SendingTab ? + EditAddressDialog::NewSendingAddress : + EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if(dlg.exec()) { diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index a9192efc84..20beb51ec4 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -39,7 +39,7 @@ public: ForEditing /**< Open address book for editing */ }; - explicit AddressBookPage(Mode mode, Tabs tab, QWidget *parent = 0); + explicit AddressBookPage(Mode mode, Tabs tab, QWidget *parent); ~AddressBookPage(); void setModel(AddressTableModel *model); diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index 4c92afcd54..1119e0861f 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -27,7 +27,7 @@ public: Decrypt /**< Ask passphrase and decrypt wallet */ }; - explicit AskPassphraseDialog(Mode mode, QWidget *parent = 0); + explicit AskPassphraseDialog(Mode mode, QWidget *parent); ~AskPassphraseDialog(); void accept(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index a1becc12ec..a44627690f 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -10,12 +10,12 @@ #include "guiconstants.h" #include "guiutil.h" #include "notificator.h" +#include "openuridialog.h" #include "optionsdialog.h" #include "optionsmodel.h" #include "rpcconsole.h" #include "walletframe.h" #include "walletmodel.h" -#include "openuridialog.h" #ifdef Q_OS_MAC #include "macdockiconhandler.h" @@ -345,7 +345,7 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers()); connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); - // Receive and report messages from network/worker thread + // Receive and report messages from client model connect(clientModel, SIGNAL(message(QString,QString,unsigned int)), this, SLOT(message(QString,QString,unsigned int))); rpcConsole->setClientModel(clientModel); @@ -460,21 +460,25 @@ void BitcoinGUI::optionsClicked() { if(!clientModel || !clientModel->getOptionsModel()) return; - OptionsDialog dlg; + + OptionsDialog dlg(this); dlg.setModel(clientModel->getOptionsModel()); dlg.exec(); } void BitcoinGUI::aboutClicked() { - AboutDialog dlg; + if(!clientModel) + return; + + AboutDialog dlg(this); dlg.setModel(clientModel); dlg.exec(); } void BitcoinGUI::openClicked() { - OpenURIDialog dlg; + OpenURIDialog dlg(this); if(dlg.exec()) { emit receivedURI(dlg.getURI()); diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp new file mode 100644 index 0000000000..4ecc040bfb --- /dev/null +++ b/src/qt/coincontroldialog.cpp @@ -0,0 +1,774 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "coincontroldialog.h" +#include "ui_coincontroldialog.h" + +#include "addresstablemodel.h" +#include "bitcoinunits.h" +#include "guiutil.h" +#include "init.h" +#include "optionsmodel.h" +#include "walletmodel.h" + +#include "coincontrol.h" +#include "main.h" +#include "wallet.h" + +#include <QApplication> +#include <QCheckBox> +#include <QColor> +#include <QCursor> +#include <QDateTime> +#include <QDialogButtonBox> +#include <QFlags> +#include <QIcon> +#include <QString> +#include <QTreeWidget> +#include <QTreeWidgetItem> + +using namespace std; +QList<qint64> CoinControlDialog::payAmounts; +CCoinControl* CoinControlDialog::coinControl = new CCoinControl(); + +CoinControlDialog::CoinControlDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CoinControlDialog), + model(0) +{ + ui->setupUi(this); + + // context menu 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); + copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this + lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this + unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this + + // context menu + contextMenu = new QMenu(); + contextMenu->addAction(copyAddressAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyAmountAction); + contextMenu->addAction(copyTransactionHashAction); + contextMenu->addSeparator(); + contextMenu->addAction(lockAction); + contextMenu->addAction(unlockAction); + + // context menu signals + connect(ui->treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + connect(copyTransactionHashAction, SIGNAL(triggered()), this, SLOT(copyTransactionHash())); + connect(lockAction, SIGNAL(triggered()), this, SLOT(lockCoin())); + connect(unlockAction, SIGNAL(triggered()), this, SLOT(unlockCoin())); + + // clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(clipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(clipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(clipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(clipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(clipboardBytes())); + connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(clipboardPriority())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(clipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(clipboardChange())); + + ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); + ui->labelCoinControlAmount->addAction(clipboardAmountAction); + ui->labelCoinControlFee->addAction(clipboardFeeAction); + ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelCoinControlBytes->addAction(clipboardBytesAction); + ui->labelCoinControlPriority->addAction(clipboardPriorityAction); + ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelCoinControlChange->addAction(clipboardChangeAction); + + // toggle tree/list mode + connect(ui->radioTreeMode, SIGNAL(toggled(bool)), this, SLOT(radioTreeMode(bool))); + connect(ui->radioListMode, SIGNAL(toggled(bool)), this, SLOT(radioListMode(bool))); + + // click on checkbox + connect(ui->treeWidget, SIGNAL(itemChanged( QTreeWidgetItem*, int)), this, SLOT(viewItemChanged( QTreeWidgetItem*, int))); + + // click on header +#if QT_VERSION < 0x050000 + ui->treeWidget->header()->setClickable(true); +#else + ui->treeWidget->header()->setSectionsClickable(true); +#endif + connect(ui->treeWidget->header(), SIGNAL(sectionClicked(int)), this, SLOT(headerSectionClicked(int))); + + // ok button + connect(ui->buttonBox, SIGNAL(clicked( QAbstractButton*)), this, SLOT(buttonBoxClicked(QAbstractButton*))); + + // (un)select all + connect(ui->pushButtonSelectAll, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked())); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84); + ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 100); + ui->treeWidget->setColumnWidth(COLUMN_LABEL, 170); + ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 290); + ui->treeWidget->setColumnWidth(COLUMN_DATE, 110); + ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 100); + ui->treeWidget->setColumnWidth(COLUMN_PRIORITY, 100); + ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transacton hash in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_AMOUNT_INT64, true); // store amount int64 in this column, but dont show it + ui->treeWidget->setColumnHidden(COLUMN_PRIORITY_INT64, true); // store priority int64 in this column, but dont show it + + // default view is sorted by amount desc + sortView(COLUMN_AMOUNT_INT64, Qt::DescendingOrder); +} + +CoinControlDialog::~CoinControlDialog() +{ + delete ui; +} + +void CoinControlDialog::setModel(WalletModel *model) +{ + this->model = model; + + if(model && model->getOptionsModel() && model->getAddressTableModel()) + { + updateView(); + updateLabelLocked(); + CoinControlDialog::updateLabels(model, this); + } +} + +// helper function str_pad +QString CoinControlDialog::strPad(QString s, int nPadLength, QString sPadding) +{ + while (s.length() < nPadLength) + s = sPadding + s; + + return s; +} + +// ok button +void CoinControlDialog::buttonBoxClicked(QAbstractButton* button) +{ + if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) + done(QDialog::Accepted); // closes the dialog +} + +// (un)select all +void CoinControlDialog::buttonSelectAllClicked() +{ + Qt::CheckState state = Qt::Checked; + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + { + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked) + { + state = Qt::Unchecked; + break; + } + } + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state); + ui->treeWidget->setEnabled(true); + if (state == Qt::Unchecked) + coinControl->UnSelectAll(); // just to be sure + CoinControlDialog::updateLabels(model, this); +} + +// context menu +void CoinControlDialog::showMenu(const QPoint &point) +{ + QTreeWidgetItem *item = ui->treeWidget->itemAt(point); + if(item) + { + contextMenuItem = item; + + // disable some items (like Copy Transaction ID, lock, unlock) for tree roots in context menu + if (item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + copyTransactionHashAction->setEnabled(true); + if (model->isLockedCoin(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt())) + { + lockAction->setEnabled(false); + unlockAction->setEnabled(true); + } + else + { + lockAction->setEnabled(true); + unlockAction->setEnabled(false); + } + } + else // this means click on parent node in tree mode -> disable all + { + copyTransactionHashAction->setEnabled(false); + lockAction->setEnabled(false); + unlockAction->setEnabled(false); + } + + // show context menu + contextMenu->exec(QCursor::pos()); + } +} + +// context menu action: copy amount +void CoinControlDialog::copyAmount() +{ + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_AMOUNT)); +} + +// context menu action: copy label +void CoinControlDialog::copyLabel() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_LABEL).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_LABEL)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_LABEL)); +} + +// context menu action: copy address +void CoinControlDialog::copyAddress() +{ + if (ui->radioTreeMode->isChecked() && contextMenuItem->text(COLUMN_ADDRESS).length() == 0 && contextMenuItem->parent()) + GUIUtil::setClipboard(contextMenuItem->parent()->text(COLUMN_ADDRESS)); + else + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_ADDRESS)); +} + +// context menu action: copy transaction id +void CoinControlDialog::copyTransactionHash() +{ + GUIUtil::setClipboard(contextMenuItem->text(COLUMN_TXHASH)); +} + +// context menu action: lock coin +void CoinControlDialog::lockCoin() +{ + if (contextMenuItem->checkState(COLUMN_CHECKBOX) == Qt::Checked) + contextMenuItem->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + + COutPoint outpt(uint256(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->lockCoin(outpt); + contextMenuItem->setDisabled(true); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed")); + updateLabelLocked(); +} + +// context menu action: unlock coin +void CoinControlDialog::unlockCoin() +{ + COutPoint outpt(uint256(contextMenuItem->text(COLUMN_TXHASH).toStdString()), contextMenuItem->text(COLUMN_VOUT_INDEX).toUInt()); + model->unlockCoin(outpt); + contextMenuItem->setDisabled(false); + contextMenuItem->setIcon(COLUMN_CHECKBOX, QIcon()); + updateLabelLocked(); +} + +// copy label "Quantity" to clipboard +void CoinControlDialog::clipboardQuantity() +{ + GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); +} + +// copy label "Amount" to clipboard +void CoinControlDialog::clipboardAmount() +{ + GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); +} + +// copy label "Fee" to clipboard +void CoinControlDialog::clipboardFee() +{ + GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); +} + +// copy label "After fee" to clipboard +void CoinControlDialog::clipboardAfterFee() +{ + GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); +} + +// copy label "Bytes" to clipboard +void CoinControlDialog::clipboardBytes() +{ + GUIUtil::setClipboard(ui->labelCoinControlBytes->text()); +} + +// copy label "Priority" to clipboard +void CoinControlDialog::clipboardPriority() +{ + GUIUtil::setClipboard(ui->labelCoinControlPriority->text()); +} + +// copy label "Low output" to clipboard +void CoinControlDialog::clipboardLowOutput() +{ + GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); +} + +// copy label "Change" to clipboard +void CoinControlDialog::clipboardChange() +{ + GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); +} + +// treeview: sort +void CoinControlDialog::sortView(int column, Qt::SortOrder order) +{ + sortColumn = column; + sortOrder = order; + ui->treeWidget->sortItems(column, order); + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? COLUMN_PRIORITY : sortColumn)), sortOrder); +} + +// treeview: clicked on header +void CoinControlDialog::headerSectionClicked(int logicalIndex) +{ + if (logicalIndex == COLUMN_CHECKBOX) // click on most left column -> do nothing + { + ui->treeWidget->header()->setSortIndicator((sortColumn == COLUMN_AMOUNT_INT64 ? COLUMN_AMOUNT : (sortColumn == COLUMN_PRIORITY_INT64 ? COLUMN_PRIORITY : sortColumn)), sortOrder); + } + else + { + if (logicalIndex == COLUMN_AMOUNT) // sort by amount + logicalIndex = COLUMN_AMOUNT_INT64; + + if (logicalIndex == COLUMN_PRIORITY) // sort by priority + logicalIndex = COLUMN_PRIORITY_INT64; + + if (sortColumn == logicalIndex) + sortOrder = ((sortOrder == Qt::AscendingOrder) ? Qt::DescendingOrder : Qt::AscendingOrder); + else + { + sortColumn = logicalIndex; + sortOrder = ((sortColumn == COLUMN_AMOUNT_INT64 || sortColumn == COLUMN_PRIORITY_INT64 || sortColumn == COLUMN_DATE || sortColumn == COLUMN_CONFIRMATIONS) ? Qt::DescendingOrder : Qt::AscendingOrder); // if amount,date,conf,priority then default => desc, else default => asc + } + + sortView(sortColumn, sortOrder); + } +} + +// toggle tree mode +void CoinControlDialog::radioTreeMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// toggle list mode +void CoinControlDialog::radioListMode(bool checked) +{ + if (checked && model) + updateView(); +} + +// checkbox clicked by user +void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX && item->text(COLUMN_TXHASH).length() == 64) // transaction hash is 64 characters (this means its a child node, so its not a parent node in tree mode) + { + COutPoint outpt(uint256(item->text(COLUMN_TXHASH).toStdString()), item->text(COLUMN_VOUT_INDEX).toUInt()); + + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) + coinControl->UnSelect(outpt); + else if (item->isDisabled()) // locked (this happens if "check all" through parent node) + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + else + coinControl->Select(outpt); + + // selection changed -> update labels + if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all + CoinControlDialog::updateLabels(model, this); + } +} + +// return human readable label for priority number +QString CoinControlDialog::getPriorityLabel(double dPriority) +{ + if (AllowFree(dPriority)) // at least medium + { + if (AllowFree(dPriority / 1000000)) return tr("highest"); + else if (AllowFree(dPriority / 100000)) return tr("higher"); + else if (AllowFree(dPriority / 10000)) return tr("high"); + else if (AllowFree(dPriority / 1000)) return tr("medium-high"); + else return tr("medium"); + } + else + { + if (AllowFree(dPriority * 10)) return tr("low-medium"); + else if (AllowFree(dPriority * 100)) return tr("low"); + else if (AllowFree(dPriority * 1000)) return tr("lower"); + else return tr("lowest"); + } +} + +// shows count of locked unspent outputs +void CoinControlDialog::updateLabelLocked() +{ + vector<COutPoint> vOutpts; + model->listLockedCoins(vOutpts); + if (vOutpts.size() > 0) + { + ui->labelLocked->setText(tr("(%1 locked)").arg(vOutpts.size())); + ui->labelLocked->setVisible(true); + } + else ui->labelLocked->setVisible(false); +} + +void CoinControlDialog::updateLabels(WalletModel *model, QDialog* dialog) +{ + if (!model) return; + + // nPayAmount + qint64 nPayAmount = 0; + bool fLowOutput = false; + bool fDust = false; + CTransaction txDummy; + foreach(const qint64 &amount, CoinControlDialog::payAmounts) + { + nPayAmount += amount; + + if (amount > 0) + { + if (amount < CENT) + fLowOutput = true; + + CTxOut txout(amount, (CScript)vector<unsigned char>(24, 0)); + txDummy.vout.push_back(txout); + if (txout.IsDust(CTransaction::nMinRelayTxFee)) + fDust = true; + } + } + + QString sPriorityLabel = ""; + int64_t nAmount = 0; + int64_t nPayFee = 0; + int64_t nAfterFee = 0; + int64_t nChange = 0; + unsigned int nBytes = 0; + unsigned int nBytesInputs = 0; + double dPriority = 0; + double dPriorityInputs = 0; + unsigned int nQuantity = 0; + int nQuantityUncompressed = 0; + + vector<COutPoint> vCoinControl; + vector<COutput> vOutputs; + coinControl->ListSelected(vCoinControl); + model->getOutputs(vCoinControl, vOutputs); + + BOOST_FOREACH(const COutput& out, vOutputs) + { + // unselect already spent, very unlikely scenario, this could happen when selected are spent elsewhere, like rpc or another computer + if (out.tx->IsSpent(out.i)) + { + uint256 txhash = out.tx->GetHash(); + COutPoint outpt(txhash, out.i); + coinControl->UnSelect(outpt); + continue; + } + + // Quantity + nQuantity++; + + // Amount + nAmount += out.tx->vout[out.i].nValue; + + // Priority + dPriorityInputs += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + + // Bytes + CTxDestination address; + if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) + { + CPubKey pubkey; + CKeyID *keyid = boost::get<CKeyID>(&address); + if (keyid && model->getPubKey(*keyid, pubkey)) + { + nBytesInputs += (pubkey.IsCompressed() ? 148 : 180); + if (!pubkey.IsCompressed()) + nQuantityUncompressed++; + } + else + nBytesInputs += 148; // in all error cases, simply assume 148 here + } + else nBytesInputs += 148; + } + + // calculation + if (nQuantity > 0) + { + // Bytes + nBytes = nBytesInputs + ((CoinControlDialog::payAmounts.size() > 0 ? CoinControlDialog::payAmounts.size() + 1 : 2) * 34) + 10; // always assume +1 output for change here + + // Priority + dPriority = dPriorityInputs / (nBytes - nBytesInputs + (nQuantityUncompressed * 29)); // 29 = 180 - 151 (uncompressed public keys are over the limit. max 151 bytes of the input are ignored for priority) + sPriorityLabel = CoinControlDialog::getPriorityLabel(dPriority); + + // Fee + int64_t nFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); + + // Min Fee + int64_t nMinFee = GetMinFee(txDummy, nBytes, AllowFree(dPriority), GMF_SEND); + + nPayFee = max(nFee, nMinFee); + + if (nPayAmount > 0) + { + nChange = nAmount - nPayFee - nPayAmount; + + // if sub-cent change is required, the fee must be raised to at least CTransaction::nMinTxFee + if (nPayFee < CTransaction::nMinTxFee && nChange > 0 && nChange < CENT) + { + if (nChange < CTransaction::nMinTxFee) // change < 0.0001 => simply move all change to fees + { + nPayFee += nChange; + nChange = 0; + } + else + { + nChange = nChange + nPayFee - CTransaction::nMinTxFee; + nPayFee = CTransaction::nMinTxFee; + } + } + + // Never create dust outputs; if we would, just add the dust to the fee. + if (nChange > 0 && nChange < CENT) + { + CTxOut txout(nChange, (CScript)vector<unsigned char>(24, 0)); + if (txout.IsDust(CTransaction::nMinRelayTxFee)) + { + nPayFee += nChange; + nChange = 0; + } + } + + if (nChange == 0) + nBytes -= 34; + } + + // after fee + nAfterFee = nAmount - nPayFee; + if (nAfterFee < 0) + nAfterFee = 0; + } + + // actually update labels + int nDisplayUnit = BitcoinUnits::BTC; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + QLabel *l1 = dialog->findChild<QLabel *>("labelCoinControlQuantity"); + QLabel *l2 = dialog->findChild<QLabel *>("labelCoinControlAmount"); + QLabel *l3 = dialog->findChild<QLabel *>("labelCoinControlFee"); + QLabel *l4 = dialog->findChild<QLabel *>("labelCoinControlAfterFee"); + QLabel *l5 = dialog->findChild<QLabel *>("labelCoinControlBytes"); + QLabel *l6 = dialog->findChild<QLabel *>("labelCoinControlPriority"); + QLabel *l7 = dialog->findChild<QLabel *>("labelCoinControlLowOutput"); + QLabel *l8 = dialog->findChild<QLabel *>("labelCoinControlChange"); + + // enable/disable "low output" and "change" + dialog->findChild<QLabel *>("labelCoinControlLowOutputText")->setEnabled(nPayAmount > 0); + dialog->findChild<QLabel *>("labelCoinControlLowOutput") ->setEnabled(nPayAmount > 0); + dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setEnabled(nPayAmount > 0); + dialog->findChild<QLabel *>("labelCoinControlChange") ->setEnabled(nPayAmount > 0); + + // stats + l1->setText(QString::number(nQuantity)); // Quantity + l2->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAmount)); // Amount + l3->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nPayFee)); // Fee + l4->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nAfterFee)); // After Fee + l5->setText(((nBytes > 0) ? "~" : "") + QString::number(nBytes)); // Bytes + l6->setText(sPriorityLabel); // Priority + l7->setText((fLowOutput ? (fDust ? tr("DUST") : tr("yes")) : tr("no"))); // Low Output / Dust + l8->setText(BitcoinUnits::formatWithUnit(nDisplayUnit, nChange)); // Change + + // turn labels "red" + l5->setStyleSheet((nBytes >= 1000) ? "color:red;" : ""); // Bytes >= 1000 + l6->setStyleSheet((!AllowFree(dPriority)) ? "color:red;" : ""); // Priority < "medium" + l7->setStyleSheet((fLowOutput) ? "color:red;" : ""); // Low Output = "yes" + l8->setStyleSheet((nChange > 0 && nChange < CENT) ? "color:red;" : ""); // Change < 0.01BTC + + // tool tips + l5->setToolTip(tr("This label turns red, if the transaction size is bigger than 1000 bytes.\n\n This means a fee of at least %1 per kb is required.\n\n Can vary +/- 1 Byte per input.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::nMinTxFee))); + l6->setToolTip(tr("Transactions with higher priority get more likely into a block.\n\nThis label turns red, if the priority is smaller than \"medium\".\n\n This means a fee of at least %1 per kb is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::nMinTxFee))); + l7->setToolTip(tr("This label turns red, if any recipient receives an amount smaller than %1.\n\n This means a fee of at least %2 is required. \n\n Amounts below 0.546 times the minimum relay fee are shown as DUST.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::nMinTxFee))); + l8->setToolTip(tr("This label turns red, if the change is smaller than %1.\n\n This means a fee of at least %2 is required.").arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CENT)).arg(BitcoinUnits::formatWithUnit(nDisplayUnit, CTransaction::nMinTxFee))); + dialog->findChild<QLabel *>("labelCoinControlBytesText") ->setToolTip(l5->toolTip()); + dialog->findChild<QLabel *>("labelCoinControlPriorityText") ->setToolTip(l6->toolTip()); + dialog->findChild<QLabel *>("labelCoinControlLowOutputText")->setToolTip(l7->toolTip()); + dialog->findChild<QLabel *>("labelCoinControlChangeText") ->setToolTip(l8->toolTip()); + + // Insufficient funds + QLabel *label = dialog->findChild<QLabel *>("labelCoinControlInsuffFunds"); + if (label) + label->setVisible(nChange < 0); +} + +void CoinControlDialog::updateView() +{ + bool treeMode = ui->radioTreeMode->isChecked(); + + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setAlternatingRowColors(!treeMode); + QFlags<Qt::ItemFlag> flgCheckbox=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags<Qt::ItemFlag> flgTristate=Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + int nDisplayUnit = BitcoinUnits::BTC; + if (model && model->getOptionsModel()) + nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); + + map<QString, vector<COutput> > mapCoins; + model->listCoins(mapCoins); + + BOOST_FOREACH(PAIRTYPE(QString, vector<COutput>) coins, mapCoins) + { + QTreeWidgetItem *itemWalletAddress = new QTreeWidgetItem(); + QString sWalletAddress = coins.first; + QString sWalletLabel = ""; + if (model->getAddressTableModel()) + sWalletLabel = model->getAddressTableModel()->labelForAddress(sWalletAddress); + if (sWalletLabel.length() == 0) + sWalletLabel = tr("(no label)"); + + if (treeMode) + { + // wallet address + ui->treeWidget->addTopLevelItem(itemWalletAddress); + + itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + for (int i = 0; i < ui->treeWidget->columnCount(); i++) + itemWalletAddress->setBackground(i, QColor(248, 247, 246)); + + // label + itemWalletAddress->setText(COLUMN_LABEL, sWalletLabel); + + // address + itemWalletAddress->setText(COLUMN_ADDRESS, sWalletAddress); + } + + int64_t nSum = 0; + double dPrioritySum = 0; + int nChildren = 0; + int nInputSum = 0; + BOOST_FOREACH(const COutput& out, coins.second) + { + int nInputSize = 0; + nSum += out.tx->vout[out.i].nValue; + nChildren++; + + QTreeWidgetItem *itemOutput; + if (treeMode) itemOutput = new QTreeWidgetItem(itemWalletAddress); + else itemOutput = new QTreeWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + + // address + CTxDestination outputAddress; + QString sAddress = ""; + if(ExtractDestination(out.tx->vout[out.i].scriptPubKey, outputAddress)) + { + sAddress = CBitcoinAddress(outputAddress).ToString().c_str(); + + // if listMode or change => show bitcoin address. In tree mode, address is not shown again for direct wallet address outputs + if (!treeMode || (!(sAddress == sWalletAddress))) + itemOutput->setText(COLUMN_ADDRESS, sAddress); + + CPubKey pubkey; + CKeyID *keyid = boost::get<CKeyID>(&outputAddress); + if (keyid && model->getPubKey(*keyid, pubkey) && !pubkey.IsCompressed()) + nInputSize = 29; // 29 = 180 - 151 (public key is 180 bytes, priority free area is 151 bytes) + } + + // label + if (!(sAddress == sWalletAddress)) // change + { + // tooltip from where the change comes from + itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress)); + itemOutput->setText(COLUMN_LABEL, tr("(change)")); + } + else if (!treeMode) + { + QString sLabel = ""; + if (model->getAddressTableModel()) + sLabel = model->getAddressTableModel()->labelForAddress(sAddress); + if (sLabel.length() == 0) + sLabel = tr("(no label)"); + itemOutput->setText(COLUMN_LABEL, sLabel); + } + + // amount + itemOutput->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, out.tx->vout[out.i].nValue)); + itemOutput->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(out.tx->vout[out.i].nValue), 15, " ")); // padding so that sorting works correctly + + // date + itemOutput->setText(COLUMN_DATE, QDateTime::fromTime_t(out.tx->GetTxTime()).toString("yy-MM-dd hh:mm")); + + // confirmations + itemOutput->setText(COLUMN_CONFIRMATIONS, strPad(QString::number(out.nDepth), 8, " ")); + + // priority + double dPriority = ((double)out.tx->vout[out.i].nValue / (nInputSize + 78)) * (out.nDepth+1); // 78 = 2 * 34 + 10 + itemOutput->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPriority)); + itemOutput->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPriority), 20, " ")); + dPrioritySum += (double)out.tx->vout[out.i].nValue * (out.nDepth+1); + nInputSum += nInputSize; + + // transaction hash + uint256 txhash = out.tx->GetHash(); + itemOutput->setText(COLUMN_TXHASH, txhash.GetHex().c_str()); + + // vout index + itemOutput->setText(COLUMN_VOUT_INDEX, QString::number(out.i)); + + // disable locked coins + if (model->isLockedCoin(txhash, out.i)) + { + COutPoint outpt(txhash, out.i); + coinControl->UnSelect(outpt); // just to be sure + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/lock_closed")); + } + + // set checkbox + if (coinControl->IsSelected(txhash, out.i)) + itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Checked); + } + + // amount + if (treeMode) + { + dPrioritySum = dPrioritySum / (nInputSum + 78); + itemWalletAddress->setText(COLUMN_CHECKBOX, "(" + QString::number(nChildren) + ")"); + itemWalletAddress->setText(COLUMN_AMOUNT, BitcoinUnits::format(nDisplayUnit, nSum)); + itemWalletAddress->setText(COLUMN_AMOUNT_INT64, strPad(QString::number(nSum), 15, " ")); + itemWalletAddress->setText(COLUMN_PRIORITY, CoinControlDialog::getPriorityLabel(dPrioritySum)); + itemWalletAddress->setText(COLUMN_PRIORITY_INT64, strPad(QString::number((int64_t)dPrioritySum), 20, " ")); + } + } + + // expand all partially selected + if (treeMode) + { + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) == Qt::PartiallyChecked) + ui->treeWidget->topLevelItem(i)->setExpanded(true); + } + + // sort view + sortView(sortColumn, sortOrder); + ui->treeWidget->setEnabled(true); +} diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h new file mode 100644 index 0000000000..b9318ca7b0 --- /dev/null +++ b/src/qt/coincontroldialog.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef COINCONTROLDIALOG_H +#define COINCONTROLDIALOG_H + +#include <QAbstractButton> +#include <QAction> +#include <QDialog> +#include <QList> +#include <QMenu> +#include <QPoint> +#include <QString> +#include <QTreeWidgetItem> + +namespace Ui { + class CoinControlDialog; +} +class WalletModel; +class CCoinControl; + +class CoinControlDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CoinControlDialog(QWidget *parent = 0); + ~CoinControlDialog(); + + void setModel(WalletModel *model); + + // static because also called from sendcoinsdialog + static void updateLabels(WalletModel*, QDialog*); + static QString getPriorityLabel(double); + + static QList<qint64> payAmounts; + static CCoinControl *coinControl; + +private: + Ui::CoinControlDialog *ui; + WalletModel *model; + int sortColumn; + Qt::SortOrder sortOrder; + + QMenu *contextMenu; + QTreeWidgetItem *contextMenuItem; + QAction *copyTransactionHashAction; + QAction *lockAction; + QAction *unlockAction; + + QString strPad(QString, int, QString); + void sortView(int, Qt::SortOrder); + void updateView(); + + enum + { + COLUMN_CHECKBOX, + COLUMN_AMOUNT, + COLUMN_LABEL, + COLUMN_ADDRESS, + COLUMN_DATE, + COLUMN_CONFIRMATIONS, + COLUMN_PRIORITY, + COLUMN_TXHASH, + COLUMN_VOUT_INDEX, + COLUMN_AMOUNT_INT64, + COLUMN_PRIORITY_INT64 + }; + +private slots: + void showMenu(const QPoint &); + void copyAmount(); + void copyLabel(); + void copyAddress(); + void copyTransactionHash(); + void lockCoin(); + void unlockCoin(); + void clipboardQuantity(); + void clipboardAmount(); + void clipboardFee(); + void clipboardAfterFee(); + void clipboardBytes(); + void clipboardPriority(); + void clipboardLowOutput(); + void clipboardChange(); + void radioTreeMode(bool); + void radioListMode(bool); + void viewItemChanged(QTreeWidgetItem*, int); + void headerSectionClicked(int); + void buttonBoxClicked(QAbstractButton*); + void buttonSelectAllClicked(); + void updateLabelLocked(); +}; + +#endif // COINCONTROLDIALOG_H diff --git a/src/qt/coincontroltreewidget.cpp b/src/qt/coincontroltreewidget.cpp new file mode 100644 index 0000000000..907b5caa05 --- /dev/null +++ b/src/qt/coincontroltreewidget.cpp @@ -0,0 +1,32 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "coincontroltreewidget.h" +#include "coincontroldialog.h" + +CoinControlTreeWidget::CoinControlTreeWidget(QWidget *parent) : + QTreeWidget(parent) +{ + +} + +void CoinControlTreeWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Space) // press spacebar -> select checkbox + { + event->ignore(); + int COLUMN_CHECKBOX = 0; + this->currentItem()->setCheckState(COLUMN_CHECKBOX, ((this->currentItem()->checkState(COLUMN_CHECKBOX) == Qt::Checked) ? Qt::Unchecked : Qt::Checked)); + } + else if (event->key() == Qt::Key_Escape) // press esc -> close dialog + { + event->ignore(); + CoinControlDialog *coinControlDialog = (CoinControlDialog*)this->parentWidget(); + coinControlDialog->done(QDialog::Accepted); + } + else + { + this->QTreeWidget::keyPressEvent(event); + } +}
\ No newline at end of file diff --git a/src/qt/coincontroltreewidget.h b/src/qt/coincontroltreewidget.h new file mode 100644 index 0000000000..a2cd34eb88 --- /dev/null +++ b/src/qt/coincontroltreewidget.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011-2013 The Bitcoin developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef COINCONTROLTREEWIDGET_H +#define COINCONTROLTREEWIDGET_H + +#include <QKeyEvent> +#include <QTreeWidget> + +class CoinControlTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + explicit CoinControlTreeWidget(QWidget *parent = 0); + +protected: + virtual void keyPressEvent(QKeyEvent *event); +}; + +#endif // COINCONTROLTREEWIDGET_H
\ No newline at end of file diff --git a/src/qt/editaddressdialog.h b/src/qt/editaddressdialog.h index a448c4b23f..6910c667cd 100644 --- a/src/qt/editaddressdialog.h +++ b/src/qt/editaddressdialog.h @@ -31,7 +31,7 @@ public: EditSendingAddress }; - explicit EditAddressDialog(Mode mode, QWidget *parent = 0); + explicit EditAddressDialog(Mode mode, QWidget *parent); ~EditAddressDialog(); void setModel(AddressTableModel *model); diff --git a/src/qt/forms/coincontroldialog.ui b/src/qt/forms/coincontroldialog.ui new file mode 100644 index 0000000000..055dd1f98d --- /dev/null +++ b/src/qt/forms/coincontroldialog.ui @@ -0,0 +1,505 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CoinControlDialog</class> + <widget class="QDialog" name="CoinControlDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1000</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Coin Control Address Selection</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutTop" stretch="0,0,0,0"> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl1"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlQuantityText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Quantity:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlQuantity"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlBytesText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Bytes:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlBytes"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl2"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlAmountText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Amount:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlAmount"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlPriorityText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Priority:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlPriority"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl3"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlFeeText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Fee:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlFee"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlLowOutputText"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Low Output:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlLowOutput"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string>no</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl4"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>10</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlAfterFeeText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>After Fee:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlAfterFee"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlChangeText"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Change:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlChange"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="QFrame" name="frame"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>40</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <widget class="QWidget" name="horizontalLayoutWidget"> + <property name="geometry"> + <rect> + <x>10</x> + <y>0</y> + <width>781</width> + <height>41</height> + </rect> + </property> + <layout class="QHBoxLayout" name="horizontalLayoutPanel" stretch="0,0,0,0,0"> + <property name="spacing"> + <number>14</number> + </property> + <item> + <widget class="QPushButton" name="pushButtonSelectAll"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>(un)select all</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioTreeMode"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Tree mode</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioListMode"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>List mode</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelLocked"> + <property name="text"> + <string>(1 locked)</string> + </property> + </widget> + </item> + <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> + </layout> + </widget> + </widget> + </item> + <item> + <widget class="CoinControlTreeWidget" name="treeWidget"> + <property name="contextMenuPolicy"> + <enum>Qt::CustomContextMenu</enum> + </property> + <property name="sortingEnabled"> + <bool>false</bool> + </property> + <property name="columnCount"> + <number>11</number> + </property> + <attribute name="headerShowSortIndicator" stdset="0"> + <bool>true</bool> + </attribute> + <attribute name="headerStretchLastSection"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string/> + </property> + </column> + <column> + <property name="text"> + <string>Amount</string> + </property> + </column> + <column> + <property name="text"> + <string notr="true">Label</string> + </property> + </column> + <column> + <property name="text"> + <string>Address</string> + </property> + </column> + <column> + <property name="text"> + <string>Date</string> + </property> + </column> + <column> + <property name="text"> + <string>Confirmations</string> + </property> + <property name="toolTip"> + <string>Confirmed</string> + </property> + </column> + <column> + <property name="text"> + <string>Priority</string> + </property> + </column> + <column> + <property name="text"> + <string/> + </property> + </column> + <column> + <property name="text"> + <string/> + </property> + </column> + <column> + <property name="text"> + <string/> + </property> + </column> + <column> + <property name="text"> + <string/> + </property> + </column> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>CoinControlTreeWidget</class> + <extends>QTreeWidget</extends> + <header>coincontroltreewidget.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index 1e4335c645..28b629b38c 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -364,6 +364,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="coinControlFeatures"> + <property name="toolTip"> + <string>Whether to show coin control features or not.</string> + </property> + <property name="text"> + <string>Display coin &control features (experts only)</string> + </property> + </widget> + </item> + <item> <spacer name="verticalSpacer_Display"> <property name="orientation"> <enum>Qt::Vertical</enum> diff --git a/src/qt/forms/sendcoinsdialog.ui b/src/qt/forms/sendcoinsdialog.ui index 7547931ff1..790d5d6c39 100644 --- a/src/qt/forms/sendcoinsdialog.ui +++ b/src/qt/forms/sendcoinsdialog.ui @@ -6,14 +6,615 @@ <rect> <x>0</x> <y>0</y> - <width>686</width> - <height>217</height> + <width>850</width> + <height>400</height> </rect> </property> <property name="windowTitle"> <string>Send Coins</string> </property> - <layout class="QVBoxLayout" name="verticalLayout"> + <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0"> + <property name="bottomMargin"> + <number>8</number> + </property> + <item> + <widget class="QFrame" name="frameCoinControl"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayoutCoinControl2"> + <property name="spacing"> + <number>-1</number> + </property> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>6</number> + </property> + <item> + <layout class="QVBoxLayout" name="verticalLayoutCoinControl" stretch="0,0,0,0,1"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>10</number> + </property> + <property name="topMargin"> + <number>10</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutCoinControl1"> + <property name="bottomMargin"> + <number>15</number> + </property> + <item> + <widget class="QLabel" name="labelCoinControlFeatures"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Coin Control Features</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutCoinControl2" stretch="0,0,0,0"> + <property name="spacing"> + <number>8</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <widget class="QPushButton" name="pushButtonCoinControl"> + <property name="styleSheet"> + <string notr="true"/> + </property> + <property name="text"> + <string>Inputs...</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelCoinControlAutomaticallySelected"> + <property name="text"> + <string>automatically selected</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelCoinControlInsuffFunds"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="styleSheet"> + <string notr="true">color:red;font-weight:bold;</string> + </property> + <property name="text"> + <string>Insufficient funds!</string> + </property> + <property name="margin"> + <number>5</number> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacerCoinControl"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QWidget" name="widgetCoinControl" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="styleSheet"> + <string notr="true"/> + </property> + <layout class="QHBoxLayout" name="horizontalLayoutCoinControl5"> + <property name="margin"> + <number>0</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutCoinControl3" stretch="0,0,0,1"> + <property name="spacing"> + <number>20</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>10</number> + </property> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl1"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>14</number> + </property> + <property name="leftMargin"> + <number>10</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlQuantityText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Quantity:</string> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlQuantity"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0</string> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlBytesText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Bytes:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlBytes"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl2"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>14</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlAmountText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Amount:</string> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlAmount"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlPriorityText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Priority:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlPriority"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string>medium</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl3"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>14</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlFeeText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Fee:</string> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlFee"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlLowOutputText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Low Output:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlLowOutput"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string>no</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QFormLayout" name="formLayoutCoinControl4"> + <property name="horizontalSpacing"> + <number>10</number> + </property> + <property name="verticalSpacing"> + <number>14</number> + </property> + <property name="leftMargin"> + <number>6</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>6</number> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="labelCoinControlAfterFeeText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>After Fee:</string> + </property> + <property name="margin"> + <number>0</number> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="labelCoinControlAfterFee"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelCoinControlChangeText"> + <property name="styleSheet"> + <string notr="true">font-weight:bold;</string> + </property> + <property name="text"> + <string>Change:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="labelCoinControlChange"> + <property name="font"> + <font> + <family>Monospace</family> + <pointsize>10</pointsize> + </font> + </property> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> + <property name="contextMenuPolicy"> + <enum>Qt::ActionsContextMenu</enum> + </property> + <property name="text"> + <string notr="true">0.00 BTC</string> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayoutCoinControl4" stretch="0,0,0"> + <property name="spacing"> + <number>12</number> + </property> + <property name="sizeConstraint"> + <enum>QLayout::SetDefaultConstraint</enum> + </property> + <property name="topMargin"> + <number>5</number> + </property> + <property name="rightMargin"> + <number>5</number> + </property> + <item> + <widget class="QCheckBox" name="checkBoxCoinControlChange"> + <property name="text"> + <string>custom change address</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEditCoinControlChange"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="labelCoinControlChangeLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string/> + </property> + <property name="margin"> + <number>3</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacerCoinControl"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>800</width> + <height>1</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + </item> <item> <widget class="QScrollArea" name="scrollArea"> <property name="widgetResizable"> @@ -24,7 +625,7 @@ <rect> <x>0</x> <y>0</y> - <width>666</width> + <width>830</width> <height>165</height> </rect> </property> diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a45131846b..2ce09b479e 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -221,10 +221,8 @@ void copyEntryData(QAbstractItemView *view, int column, int role) if(!selection.isEmpty()) { - // Copy first item (global clipboard) - QApplication::clipboard()->setText(selection.at(0).data(role).toString(), QClipboard::Clipboard); - // Copy first item (global mouse selection for e.g. X11 - NOP on Windows) - QApplication::clipboard()->setText(selection.at(0).data(role).toString(), QClipboard::Selection); + // Copy first item + setClipboard(selection.at(0).data(role).toString()); } } @@ -633,4 +631,10 @@ void HelpMessageBox::showOrPrint() #endif } +void setClipboard(const QString& str) +{ + QApplication::clipboard()->setText(str, QClipboard::Clipboard); + QApplication::clipboard()->setText(str, QClipboard::Selection); +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index b86fd9117c..14d4ff17c1 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -55,6 +55,8 @@ namespace GUIUtil */ void copyEntryData(QAbstractItemView *view, int column, int role=Qt::EditRole); + void setClipboard(const QString& str); + /** Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when no suffix is provided by the user. diff --git a/src/qt/openuridialog.h b/src/qt/openuridialog.h index 3b9ff0a8e1..28da7d6d9d 100644 --- a/src/qt/openuridialog.h +++ b/src/qt/openuridialog.h @@ -16,7 +16,7 @@ class OpenURIDialog : public QDialog Q_OBJECT public: - explicit OpenURIDialog(QWidget *parent = 0); + explicit OpenURIDialog(QWidget *parent); ~OpenURIDialog(); QString getURI(); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 1e91a877a9..0a569d16f4 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -155,6 +155,7 @@ void OptionsDialog::setMapper() mapper->addMapping(ui->lang, OptionsModel::Language); mapper->addMapping(ui->unit, OptionsModel::DisplayUnit); mapper->addMapping(ui->displayAddresses, OptionsModel::DisplayAddresses); + mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); } void OptionsDialog::enableApplyButton() diff --git a/src/qt/optionsdialog.h b/src/qt/optionsdialog.h index 05234f645b..0181905a8c 100644 --- a/src/qt/optionsdialog.h +++ b/src/qt/optionsdialog.h @@ -21,7 +21,7 @@ class OptionsDialog : public QDialog Q_OBJECT public: - explicit OptionsDialog(QWidget *parent = 0); + explicit OptionsDialog(QWidget *parent); ~OptionsDialog(); void setModel(OptionsModel *model); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 65c017f088..15a873d2bd 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -59,6 +59,7 @@ void OptionsModel::Init() fMinimizeOnClose = settings.value("fMinimizeOnClose", false).toBool(); nTransactionFee = settings.value("nTransactionFee").toLongLong(); language = settings.value("language", "").toString(); + fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); // These are shared with core Bitcoin; we want // command-line options to override the GUI settings: @@ -207,6 +208,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return QVariant(bDisplayAddresses); case Language: return settings.value("language", ""); + case CoinControlFeatures: + return QVariant(fCoinControlFeatures); default: return QVariant(); } @@ -275,6 +278,7 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case Fee: nTransactionFee = value.toLongLong(); settings.setValue("nTransactionFee", (qint64) nTransactionFee); + emit transactionFeeChanged(nTransactionFee); break; case DisplayUnit: nDisplayUnit = value.toInt(); @@ -288,6 +292,11 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case Language: settings.setValue("language", value); break; + case CoinControlFeatures: + fCoinControlFeatures = value.toBool(); + settings.setValue("fCoinControlFeatures", fCoinControlFeatures); + emit coinControlFeaturesChanged(fCoinControlFeatures); + break; default: break; } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 2d41cf889d..5bb563cc36 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -21,18 +21,19 @@ public: explicit OptionsModel(QObject *parent = 0); enum OptionID { - StartAtStartup, // bool - MinimizeToTray, // bool - MapPortUPnP, // bool - MinimizeOnClose, // bool - ProxyUse, // bool - ProxyIP, // QString - ProxyPort, // int - ProxySocksVersion, // int - Fee, // qint64 - DisplayUnit, // BitcoinUnits::Unit - DisplayAddresses, // bool - Language, // QString + StartAtStartup, // bool + MinimizeToTray, // bool + MapPortUPnP, // bool + MinimizeOnClose, // bool + ProxyUse, // bool + ProxyIP, // QString + ProxyPort, // int + ProxySocksVersion, // int + Fee, // qint64 + DisplayUnit, // BitcoinUnits::Unit + DisplayAddresses, // bool + Language, // QString + CoinControlFeatures, // bool OptionIDRowCount, }; @@ -54,6 +55,7 @@ public: bool getDisplayAddresses() { return bDisplayAddresses; } QString getLanguage() { return language; } bool getProxySettings(QString& proxyIP, quint16 &proxyPort) const; + bool getCoinControlFeatures() { return fCoinControlFeatures; } private: int nDisplayUnit; @@ -61,9 +63,12 @@ private: bool fMinimizeToTray; bool fMinimizeOnClose; QString language; + bool fCoinControlFeatures; signals: void displayUnitChanged(int unit); + void transactionFeeChanged(qint64); + void coinControlFeaturesChanged(bool); }; #endif // OPTIONSMODEL_H diff --git a/src/qt/receiverequestdialog.cpp b/src/qt/receiverequestdialog.cpp index a8a1beee6e..7e92715df8 100644 --- a/src/qt/receiverequestdialog.cpp +++ b/src/qt/receiverequestdialog.cpp @@ -11,11 +11,11 @@ #include "optionsmodel.h" #include "walletmodel.h" -#include <QPixmap> #include <QClipboard> -#include <QMouseEvent> #include <QDrag> #include <QMimeData> +#include <QMouseEvent> +#include <QPixmap> #if QT_VERSION < 0x050000 #include <QUrl> #endif @@ -177,13 +177,10 @@ void ReceiveRequestDialog::update() void ReceiveRequestDialog::on_btnCopyURI_clicked() { - QString uri = GUIUtil::formatBitcoinURI(info); - QApplication::clipboard()->setText(uri, QClipboard::Clipboard); - QApplication::clipboard()->setText(uri, QClipboard::Selection); + GUIUtil::setClipboard(GUIUtil::formatBitcoinURI(info)); } void ReceiveRequestDialog::on_btnCopyAddress_clicked() { - QApplication::clipboard()->setText(info.address, QClipboard::Clipboard); - QApplication::clipboard()->setText(info.address, QClipboard::Selection); + GUIUtil::setClipboard(info.address); } diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 1370d0b103..6fbf197728 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -19,7 +19,7 @@ class RPCConsole: public QDialog Q_OBJECT public: - explicit RPCConsole(QWidget *parent = 0); + explicit RPCConsole(QWidget *parent); ~RPCConsole(); void setClientModel(ClientModel *model); diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index b9c5eb08d6..0adf24691e 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -5,13 +5,16 @@ #include "sendcoinsdialog.h" #include "ui_sendcoinsdialog.h" +#include "addresstablemodel.h" #include "bitcoinunits.h" +#include "coincontroldialog.h" #include "guiutil.h" #include "optionsmodel.h" #include "sendcoinsentry.h" #include "walletmodel.h" #include "base58.h" +#include "coincontrol.h" #include "ui_interface.h" #include <QMessageBox> @@ -30,12 +33,47 @@ SendCoinsDialog::SendCoinsDialog(QWidget *parent) : ui->clearButton->setIcon(QIcon()); ui->sendButton->setIcon(QIcon()); #endif +#if QT_VERSION >= 0x040700 + ui->lineEditCoinControlChange->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); +#endif addEntry(); connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry())); connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + // Coin Control + ui->lineEditCoinControlChange->setFont(GUIUtil::bitcoinAddressFont()); + connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked())); + connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int))); + connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &))); + + // Coin Control: clipboard actions + QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); + QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this); + QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this); + QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this); + QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this); + QAction *clipboardPriorityAction = new QAction(tr("Copy priority"), this); + QAction *clipboardLowOutputAction = new QAction(tr("Copy low output"), this); + QAction *clipboardChangeAction = new QAction(tr("Copy change"), this); + connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity())); + connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount())); + connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee())); + connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee())); + connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes())); + connect(clipboardPriorityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardPriority())); + connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput())); + connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange())); + ui->labelCoinControlQuantity->addAction(clipboardQuantityAction); + ui->labelCoinControlAmount->addAction(clipboardAmountAction); + ui->labelCoinControlFee->addAction(clipboardFeeAction); + ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction); + ui->labelCoinControlBytes->addAction(clipboardBytesAction); + ui->labelCoinControlPriority->addAction(clipboardPriorityAction); + ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction); + ui->labelCoinControlChange->addAction(clipboardChangeAction); + fNewRecipientAllowed = true; } @@ -57,6 +95,13 @@ void SendCoinsDialog::setModel(WalletModel *model) setBalance(model->getBalance(), model->getUnconfirmedBalance(), model->getImmatureBalance()); connect(model, SIGNAL(balanceChanged(qint64, qint64, qint64)), this, SLOT(setBalance(qint64, qint64, qint64))); connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + + // Coin Control + connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels())); + connect(model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool))); + connect(model->getOptionsModel(), SIGNAL(transactionFeeChanged(qint64)), this, SLOT(coinControlUpdateLabels())); + ui->frameCoinControl->setVisible(model->getOptionsModel()->getCoinControlFeatures()); + coinControlUpdateLabels(); } } @@ -144,7 +189,12 @@ void SendCoinsDialog::on_sendButton_clicked() // prepare transaction for getting txFee earlier WalletModelTransaction currentTransaction(recipients); - WalletModel::SendCoinsReturn prepareStatus = model->prepareTransaction(currentTransaction); + WalletModel::SendCoinsReturn prepareStatus; + if (model->getOptionsModel()->getCoinControlFeatures()) // coin control enabled + prepareStatus = model->prepareTransaction(currentTransaction, CoinControlDialog::coinControl); + else + prepareStatus = model->prepareTransaction(currentTransaction); + // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), currentTransaction.getTransactionFee())); @@ -192,6 +242,8 @@ void SendCoinsDialog::on_sendButton_clicked() if (sendStatus.status == WalletModel::OK) { accept(); + CoinControlDialog::coinControl->UnSelectAll(); + coinControlUpdateLabels(); } fNewRecipientAllowed = true; } @@ -226,6 +278,7 @@ SendCoinsEntry *SendCoinsDialog::addEntry() entry->setModel(model); ui->entries->addWidget(entry); connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*))); + connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels())); updateRemoveEnabled(); @@ -253,6 +306,8 @@ void SendCoinsDialog::updateRemoveEnabled() } } setupTabChain(0); + + coinControlUpdateLabels(); } void SendCoinsDialog::removeEntry(SendCoinsEntry* entry) @@ -317,6 +372,7 @@ void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv) } entry->setValue(rv); + coinControlUpdateLabels(); } bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) @@ -404,3 +460,156 @@ void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn emit message(tr("Send Coins"), msgParams.first, msgParams.second); } + +// Coin Control: copy label "Quantity" to clipboard +void SendCoinsDialog::coinControlClipboardQuantity() +{ + GUIUtil::setClipboard(ui->labelCoinControlQuantity->text()); +} + +// Coin Control: copy label "Amount" to clipboard +void SendCoinsDialog::coinControlClipboardAmount() +{ + GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" "))); +} + +// Coin Control: copy label "Fee" to clipboard +void SendCoinsDialog::coinControlClipboardFee() +{ + GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" "))); +} + +// Coin Control: copy label "After fee" to clipboard +void SendCoinsDialog::coinControlClipboardAfterFee() +{ + GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" "))); +} + +// Coin Control: copy label "Bytes" to clipboard +void SendCoinsDialog::coinControlClipboardBytes() +{ + GUIUtil::setClipboard(ui->labelCoinControlBytes->text()); +} + +// Coin Control: copy label "Priority" to clipboard +void SendCoinsDialog::coinControlClipboardPriority() +{ + GUIUtil::setClipboard(ui->labelCoinControlPriority->text()); +} + +// Coin Control: copy label "Low output" to clipboard +void SendCoinsDialog::coinControlClipboardLowOutput() +{ + GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text()); +} + +// Coin Control: copy label "Change" to clipboard +void SendCoinsDialog::coinControlClipboardChange() +{ + GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" "))); +} + +// Coin Control: settings menu - coin control enabled/disabled by user +void SendCoinsDialog::coinControlFeatureChanged(bool checked) +{ + ui->frameCoinControl->setVisible(checked); + + if (!checked && model) // coin control features disabled + CoinControlDialog::coinControl->SetNull(); +} + +// Coin Control: button inputs -> show actual coin control dialog +void SendCoinsDialog::coinControlButtonClicked() +{ + CoinControlDialog dlg; + dlg.setModel(model); + dlg.exec(); + coinControlUpdateLabels(); +} + +// Coin Control: checkbox custom change address +void SendCoinsDialog::coinControlChangeChecked(int state) +{ + if (model) + { + if (state == Qt::Checked) + CoinControlDialog::coinControl->destChange = CBitcoinAddress(ui->lineEditCoinControlChange->text().toStdString()).Get(); + else + CoinControlDialog::coinControl->destChange = CNoDestination(); + } + + ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked)); + ui->labelCoinControlChangeLabel->setVisible((state == Qt::Checked)); +} + +// Coin Control: custom change address changed +void SendCoinsDialog::coinControlChangeEdited(const QString & text) +{ + if (model) + { + CoinControlDialog::coinControl->destChange = CBitcoinAddress(text.toStdString()).Get(); + + // label for the change address + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}"); + if (text.isEmpty()) + ui->labelCoinControlChangeLabel->setText(""); + else if (!CBitcoinAddress(text.toStdString()).IsValid()) + { + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address")); + } + else + { + QString associatedLabel = model->getAddressTableModel()->labelForAddress(text); + if (!associatedLabel.isEmpty()) + ui->labelCoinControlChangeLabel->setText(associatedLabel); + else + { + CPubKey pubkey; + CKeyID keyid; + CBitcoinAddress(text.toStdString()).GetKeyID(keyid); + if (model->getPubKey(keyid, pubkey)) + ui->labelCoinControlChangeLabel->setText(tr("(no label)")); + else + { + ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}"); + ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address")); + } + } + } + } +} + +// Coin Control: update labels +void SendCoinsDialog::coinControlUpdateLabels() +{ + if (!model || !model->getOptionsModel() || !model->getOptionsModel()->getCoinControlFeatures()) + return; + + // set pay amounts + CoinControlDialog::payAmounts.clear(); + for(int i = 0; i < ui->entries->count(); ++i) + { + SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget()); + if(entry) + CoinControlDialog::payAmounts.append(entry->getValue().amount); + } + + if (CoinControlDialog::coinControl->HasSelected()) + { + // actual coin control calculation + CoinControlDialog::updateLabels(model, this); + + // show coin control stats + ui->labelCoinControlAutomaticallySelected->hide(); + ui->widgetCoinControl->show(); + } + else + { + // hide coin control stats + ui->labelCoinControlAutomaticallySelected->show(); + ui->widgetCoinControl->hide(); + ui->labelCoinControlInsuffFunds->hide(); + } +} + diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h index 9d5f34f0c2..4327e8e382 100644 --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -8,6 +8,7 @@ #include "walletmodel.h" #include <QDialog> +#include <QString> class OptionsModel; class SendCoinsEntry; @@ -62,6 +63,19 @@ private slots: void on_sendButton_clicked(); void removeEntry(SendCoinsEntry* entry); void updateDisplayUnit(); + void coinControlFeatureChanged(bool); + void coinControlButtonClicked(); + void coinControlChangeChecked(int); + void coinControlChangeEdited(const QString &); + void coinControlUpdateLabels(); + void coinControlClipboardQuantity(); + void coinControlClipboardAmount(); + void coinControlClipboardFee(); + void coinControlClipboardAfterFee(); + void coinControlClipboardBytes(); + void coinControlClipboardPriority(); + void coinControlClipboardLowOutput(); + void coinControlClipboardChange(); signals: // Fired when a message should be reported to the user diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index 2d240f1fe5..2641a66af4 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -27,7 +27,6 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : ui->payToLayout->setSpacing(4); #endif #if QT_VERSION >= 0x040700 - /* Do not move this to the XML file, Qt before 4.7 will choke on it */ ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); ui->payTo->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); #endif @@ -75,6 +74,8 @@ void SendCoinsEntry::setModel(WalletModel *model) if (model && model->getOptionsModel()) connect(model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); + connect(ui->payAmount, SIGNAL(textChanged()), this, SIGNAL(payAmountChanged())); + clear(); } diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 6fc36f9787..1c4ddaa8ef 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -51,6 +51,7 @@ public slots: signals: void removeEntry(SendCoinsEntry *entry); + void payAmountChanged(); private slots: void on_deleteButton_clicked(); diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 0fa51cb92a..e319f5075a 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -25,8 +25,7 @@ SignVerifyMessageDialog::SignVerifyMessageDialog(QWidget *parent) : { ui->setupUi(this); -#if (QT_VERSION >= 0x040700) - /* Do not move this to the XML file, Qt before 4.7 will choke on it */ +#if QT_VERSION >= 0x040700 ui->addressIn_SM->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); ui->signatureOut_SM->setPlaceholderText(tr("Click \"Sign Message\" to generate signature")); @@ -73,7 +72,6 @@ void SignVerifyMessageDialog::setAddress_VM(const QString &address) void SignVerifyMessageDialog::showTab_SM(bool fShow) { ui->tabWidget->setCurrentIndex(0); - if (fShow) this->show(); } @@ -164,7 +162,7 @@ void SignVerifyMessageDialog::on_signMessageButton_SM_clicked() void SignVerifyMessageDialog::on_copySignatureButton_SM_clicked() { - QApplication::clipboard()->setText(ui->signatureOut_SM->text()); + GUIUtil::setClipboard(ui->signatureOut_SM->text()); } void SignVerifyMessageDialog::on_clearButton_SM_clicked() diff --git a/src/qt/signverifymessagedialog.h b/src/qt/signverifymessagedialog.h index c741450b8a..bba861649a 100644 --- a/src/qt/signverifymessagedialog.h +++ b/src/qt/signverifymessagedialog.h @@ -18,7 +18,7 @@ class SignVerifyMessageDialog : public QDialog Q_OBJECT public: - explicit SignVerifyMessageDialog(QWidget *parent = 0); + explicit SignVerifyMessageDialog(QWidget *parent); ~SignVerifyMessageDialog(); void setModel(WalletModel *model); diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index 428261693d..a0c3ce62aa 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -83,14 +83,12 @@ TransactionView::TransactionView(QWidget *parent) : addressWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 - /* Do not move this to the XML file, Qt before 4.7 will choke on it */ addressWidget->setPlaceholderText(tr("Enter address or label to search")); #endif hlayout->addWidget(addressWidget); amountWidget = new QLineEdit(this); #if QT_VERSION >= 0x040700 - /* Do not move this to the XML file, Qt before 4.7 will choke on it */ amountWidget->setPlaceholderText(tr("Min amount")); #endif #ifdef Q_OS_MAC @@ -355,10 +353,10 @@ void TransactionView::editLabel() // Determine type of address, launch appropriate editor dialog type QString type = modelIdx.data(AddressTableModel::TypeRole).toString(); - EditAddressDialog dlg(type==AddressTableModel::Receive - ? EditAddressDialog::EditReceivingAddress - : EditAddressDialog::EditSendingAddress, - this); + EditAddressDialog dlg( + type == AddressTableModel::Receive + ? EditAddressDialog::EditReceivingAddress + : EditAddressDialog::EditSendingAddress, this); dlg.setModel(addressBook); dlg.loadRow(idx); dlg.exec(); @@ -367,7 +365,7 @@ void TransactionView::editLabel() { // Add sending address EditAddressDialog dlg(EditAddressDialog::NewSendingAddress, - this); + this); dlg.setModel(addressBook); dlg.setAddress(address); dlg.exec(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index b1d770e1a7..2470af41a0 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -47,8 +47,19 @@ WalletModel::~WalletModel() unsubscribeFromCoreSignals(); } -qint64 WalletModel::getBalance() const +qint64 WalletModel::getBalance(const CCoinControl *coinControl) const { + if (coinControl) + { + qint64 nBalance = 0; + std::vector<COutput> vCoins; + wallet->AvailableCoins(vCoins, true, coinControl); + BOOST_FOREACH(const COutput& out, vCoins) + nBalance += out.tx->vout[out.i].nValue; + + return nBalance; + } + return wallet->GetBalance(); } @@ -136,7 +147,7 @@ bool WalletModel::validateAddress(const QString &address) return addressParsed.IsValid(); } -WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl) { qint64 total = 0; QList<SendCoinsRecipient> recipients = transaction.getRecipients(); @@ -197,12 +208,14 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact return DuplicateAddress; } - if(total > getBalance()) + qint64 nBalance = getBalance(coinControl); + + if(total > nBalance) { return AmountExceedsBalance; } - if((total + nTransactionFee) > getBalance()) + if((total + nTransactionFee) > nBalance) { transaction.setTransactionFee(nTransactionFee); return SendCoinsReturn(AmountWithFeeExceedsBalance); @@ -217,12 +230,12 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact CWalletTx *newTx = transaction.getTransaction(); CReserveKey *keyChange = transaction.getPossibleKeyChange(); - bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason); + bool fCreated = wallet->CreateTransaction(vecSend, *newTx, *keyChange, nFeeRequired, strFailReason, coinControl); transaction.setTransactionFee(nFeeRequired); if(!fCreated) { - if((total + nFeeRequired) > wallet->GetBalance()) + if((total + nFeeRequired) > nBalance) { return SendCoinsReturn(AmountWithFeeExceedsBalance); } @@ -458,3 +471,72 @@ void WalletModel::UnlockContext::CopyFrom(const UnlockContext& rhs) *this = rhs; rhs.relock = false; } + +bool WalletModel::getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const +{ + return wallet->GetPubKey(address, vchPubKeyOut); +} + +// returns a list of COutputs from COutPoints +void WalletModel::getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs) +{ + BOOST_FOREACH(const COutPoint& outpoint, vOutpoints) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain()); + vOutputs.push_back(out); + } +} + +// AvailableCoins + LockedCoins grouped by wallet address (put change in one group with wallet address) +void WalletModel::listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const +{ + std::vector<COutput> vCoins; + wallet->AvailableCoins(vCoins); + + std::vector<COutPoint> vLockedCoins; + wallet->ListLockedCoins(vLockedCoins); + + // add locked coins + BOOST_FOREACH(const COutPoint& outpoint, vLockedCoins) + { + if (!wallet->mapWallet.count(outpoint.hash)) continue; + COutput out(&wallet->mapWallet[outpoint.hash], outpoint.n, wallet->mapWallet[outpoint.hash].GetDepthInMainChain()); + vCoins.push_back(out); + } + + BOOST_FOREACH(const COutput& out, vCoins) + { + COutput cout = out; + + while (wallet->IsChange(cout.tx->vout[cout.i]) && cout.tx->vin.size() > 0 && wallet->IsMine(cout.tx->vin[0])) + { + if (!wallet->mapWallet.count(cout.tx->vin[0].prevout.hash)) break; + cout = COutput(&wallet->mapWallet[cout.tx->vin[0].prevout.hash], cout.tx->vin[0].prevout.n, 0); + } + + CTxDestination address; + if(!ExtractDestination(cout.tx->vout[cout.i].scriptPubKey, address)) continue; + mapCoins[CBitcoinAddress(address).ToString().c_str()].push_back(out); + } +} + +bool WalletModel::isLockedCoin(uint256 hash, unsigned int n) const +{ + return wallet->IsLockedCoin(hash, n); +} + +void WalletModel::lockCoin(COutPoint& output) +{ + wallet->LockCoin(output); +} + +void WalletModel::unlockCoin(COutPoint& output) +{ + wallet->UnlockCoin(output); +} + +void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts) +{ + wallet->ListLockedCoins(vOutpts); +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index f39e9dfca0..32ddbbc6f6 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -10,6 +10,9 @@ #include "allocators.h" /* for SecureString */ +#include <map> +#include <vector> + #include <QObject> class AddressTableModel; @@ -17,7 +20,13 @@ class OptionsModel; class TransactionTableModel; class WalletModelTransaction; +class CCoinControl; +class CKeyID; +class COutPoint; +class COutput; +class CPubKey; class CWallet; +class uint256; QT_BEGIN_NAMESPACE class QTimer; @@ -80,7 +89,7 @@ public: AddressTableModel *getAddressTableModel(); TransactionTableModel *getTransactionTableModel(); - qint64 getBalance() const; + qint64 getBalance(const CCoinControl *coinControl = NULL) const; qint64 getUnconfirmedBalance() const; qint64 getImmatureBalance() const; int getNumTransactions() const; @@ -92,13 +101,13 @@ public: // Return status record for SendCoins, contains error id + information struct SendCoinsReturn { - SendCoinsReturn(StatusCode status): + SendCoinsReturn(StatusCode status = Aborted): status(status) {} StatusCode status; }; // prepare transaction for getting txfee before sending coins - SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction); + SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl *coinControl = NULL); // Send coins to a list of recipients SendCoinsReturn sendCoins(WalletModelTransaction &transaction); @@ -133,6 +142,15 @@ public: UnlockContext requestUnlock(); + bool getPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const; + void getOutputs(const std::vector<COutPoint>& vOutpoints, std::vector<COutput>& vOutputs); + void listCoins(std::map<QString, std::vector<COutput> >& mapCoins) const; + + bool isLockedCoin(uint256 hash, unsigned int n) const; + void lockCoin(COutPoint& output); + void unlockCoin(COutPoint& output); + void listLockedCoins(std::vector<COutPoint>& vOutpts); + private: CWallet *wallet; diff --git a/src/script.cpp b/src/script.cpp index fe29a88613..2b66bc73d6 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1905,7 +1905,7 @@ void CScript::SetMultisig(int nRequired, const std::vector<CPubKey>& keys) bool CScriptCompressor::IsToKeyID(CKeyID &hash) const { - if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 + if (script.size() == 25 && script[0] == OP_DUP && script[1] == OP_HASH160 && script[2] == 20 && script[23] == OP_EQUALVERIFY && script[24] == OP_CHECKSIG) { memcpy(&hash, &script[3], 20); diff --git a/src/wallet.cpp b/src/wallet.cpp index bd147a454d..db957cbd05 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -6,6 +6,7 @@ #include "wallet.h" #include "base58.h" +#include "coincontrol.h" #include "net.h" #include <inttypes.h> @@ -1003,7 +1004,7 @@ int64_t CWallet::GetImmatureBalance() const } // populate vCoins with vector of spendable COutputs -void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const +void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const { vCoins.clear(); @@ -1024,8 +1025,9 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed) const for (unsigned int i = 0; i < pcoin->vout.size(); i++) { if (!(pcoin->IsSpent(i)) && IsMine(pcoin->vout[i]) && - !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0) - vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); + !IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 && + (!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i))) + vCoins.push_back(COutput(pcoin, i, pcoin->GetDepthInMainChain())); } } } @@ -1175,10 +1177,21 @@ bool CWallet::SelectCoinsMinConf(int64_t nTargetValue, int nConfMine, int nConfT return true; } -bool CWallet::SelectCoins(int64_t nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const +bool CWallet::SelectCoins(int64_t nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl* coinControl) const { vector<COutput> vCoins; - AvailableCoins(vCoins); + AvailableCoins(vCoins, true, coinControl); + + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coinControl && coinControl->HasSelected()) + { + BOOST_FOREACH(const COutput& out, vCoins) + { + nValueRet += out.tx->vout[out.i].nValue; + setCoinsRet.insert(make_pair(out.tx, out.i)); + } + return (nValueRet >= nTargetValue); + } return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) || SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) || @@ -1189,7 +1202,7 @@ bool CWallet::SelectCoins(int64_t nTargetValue, set<pair<const CWalletTx*,unsign bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason) + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { int64_t nValue = 0; BOOST_FOREACH (const PAIRTYPE(CScript, int64_t)& s, vecSend) @@ -1236,7 +1249,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, // Choose coins to use set<pair<const CWalletTx*,unsigned int> > setCoins; int64_t nValueIn = 0; - if (!SelectCoins(nTotalValue, setCoins, nValueIn)) + if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl)) { strFailReason = _("Insufficient funds"); return false; @@ -1264,22 +1277,31 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, if (nChange > 0) { - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool - CPubKey vchPubKey; - assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked - // Fill a vout to ourself // TODO: pass in scriptChange instead of reservekey so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; - scriptChange.SetDestination(vchPubKey.GetID()); + + // coin control: send change to custom address + if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange)) + scriptChange.SetDestination(coinControl->destChange); + + // no coin control: send change to newly generated address + else + { + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool + CPubKey vchPubKey; + assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + + scriptChange.SetDestination(vchPubKey.GetID()); + } CTxOut newTxOut(nChange, scriptChange); @@ -1333,7 +1355,7 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, // Check that enough fee is included int64_t nPayFee = nTransactionFee * (1 + (int64_t)nBytes / 1000); bool fAllowFree = AllowFree(dPriority); - int64_t nMinFee = GetMinFee(wtxNew, fAllowFree, GMF_SEND); + int64_t nMinFee = GetMinFee(wtxNew, nBytes, fAllowFree, GMF_SEND); if (nFeeRet < max(nPayFee, nMinFee)) { nFeeRet = max(nPayFee, nMinFee); @@ -1352,11 +1374,11 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64_t> >& vecSend, } bool CWallet::CreateTransaction(CScript scriptPubKey, int64_t nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason) + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl* coinControl) { vector< pair<CScript, int64_t> > vecSend; vecSend.push_back(make_pair(scriptPubKey, nValue)); - return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason); + return CreateTransaction(vecSend, wtxNew, reservekey, nFeeRet, strFailReason, coinControl); } // Call after CreateTransaction unless you want to abort diff --git a/src/wallet.h b/src/wallet.h index 5c38d7a1a0..90209122fd 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -24,6 +24,7 @@ #include <vector> class CAccountingEntry; +class CCoinControl; class COutput; class CReserveKey; class CScript; @@ -87,7 +88,7 @@ public: class CWallet : public CCryptoKeyStore, public CWalletInterface { private: - bool SelectCoins(int64_t nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const; + bool SelectCoins(int64_t nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet, const CCoinControl *coinControl = NULL) const; CWalletDB *pwalletdbEncryption; @@ -152,7 +153,7 @@ public: // check whether we are allowed to upgrade (or already support) to the named feature bool CanSupportFeature(enum WalletFeature wf) { return nWalletMaxVersion >= wf; } - void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true) const; + void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL) const; bool SelectCoinsMinConf(int64_t nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, int64_t& nValueRet) const; bool IsLockedCoin(uint256 hash, unsigned int n) const; void LockCoin(COutPoint& output); @@ -212,9 +213,9 @@ public: int64_t GetUnconfirmedBalance() const; int64_t GetImmatureBalance() const; bool CreateTransaction(const std::vector<std::pair<CScript, int64_t> >& vecSend, - CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason); + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); bool CreateTransaction(CScript scriptPubKey, int64_t nValue, - CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason); + CWalletTx& wtxNew, CReserveKey& reservekey, int64_t& nFeeRet, std::string& strFailReason, const CCoinControl *coinControl = NULL); bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey); std::string SendMoney(CScript scriptPubKey, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); std::string SendMoneyToDestination(const CTxDestination &address, int64_t nValue, CWalletTx& wtxNew, bool fAskFee=false); diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 061b2af1b7..2dc6594e93 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -81,8 +81,8 @@ bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, c return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); } -bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, - const std::vector<unsigned char>& vchCryptedSecret, +bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, + const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata &keyMeta) { const bool fEraseUnencryptedKey = true; @@ -428,7 +428,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, CKey key; CPrivKey pkey; uint256 hash = 0; - + if (strType == "key") { wss.nKeys++; @@ -438,7 +438,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> wkey; pkey = wkey.vchPrivKey; } - + // Old wallets store keys as "key" [pubkey] => [privkey] // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key // using EC operations as a checksum. @@ -449,9 +449,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> hash; } catch(...){} - + bool fSkipCheck = false; - + if (hash != 0) { // hash pubkey/privkey to accelerate wallet load @@ -459,16 +459,16 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, vchKey.reserve(vchPubKey.size() + pkey.size()); vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); vchKey.insert(vchKey.end(), pkey.begin(), pkey.end()); - + if (Hash(vchKey.begin(), vchKey.end()) != hash) { strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt"; return false; } - + fSkipCheck = true; } - + if (!key.Load(pkey, vchPubKey, fSkipCheck)) { strErr = "Error reading wallet database: CPrivKey corrupt"; |