diff --git a/.directory b/.directory new file mode 100644 index 0000000000000000000000000000000000000000..de51075e152260b6d88c4d3f721338105408795d --- /dev/null +++ b/.directory @@ -0,0 +1,6 @@ +[Dolphin] +Timestamp=2014,2,16,12,25,25 +Version=3 + +[Settings] +HiddenFilesShown=true diff --git a/Makefile b/Makefile index 15d694ebd173631ee9f9b9be2c87cf665ebed6fd..4afec396e08904eb71d3deeb545f8a91d47ddf34 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ RESOURCE_DIR = res/ui COMPILED_DIR = src/cutecoin/gen_resources #UI files to compile -UI_FILES = mainwindow.ui addAccountDialog.ui addCommunityDialog.ui communityTabWidget.ui +UI_FILES = mainwindow.ui addAccountDialog.ui addCommunityDialog.ui communityTabWidget.ui issuanceDialog.ui #Qt resource files to compile RESOURCES = diff --git a/README.md b/README.md index 9f0c9a4c1fecb41324c24502b648b47149904eb8..7787c391a25d7edd4740f94707550b5457fd6598 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,31 @@ cutecoin ======== -Qt Client for Ucoin project +Qt Client for [Ucoin](http://www.ucoin.io) project. + + +## Goal features + * Ucoin account management via wallets and communities + * Multi-currency + * Multi-community + * Multi-wallets + * Contacts messaging + * User-friendly coins transfer + * On-the-fly and automatic coins fusion and divisions for transactions + * Coins issuance policies : minimal space, minimal changes + * Community membership management via a voting interface + +## Current state +### Done (master branch) + * Accounts management + * Communities viewing + +### Work in progress (dev branch) + * Wallets viewing + +### Todo + * Transfer + * Coins issuance + * Coins issuance policies + * Contacts and messaging + * Voting \ No newline at end of file diff --git a/res/ui/communityTabWidget.ui b/res/ui/communityTabWidget.ui index 41c5959856af00f6e1850d70bf949e6b4479c4b7..2c499cdbec0cbb44880b22ca554bfdb9ff92abe0 100644 --- a/res/ui/communityTabWidget.ui +++ b/res/ui/communityTabWidget.ui @@ -33,7 +33,14 @@ <item> <widget class="QLabel" name="label_2"> <property name="text"> - <string>Last issuance</string> + <string>Last issuances</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="issuanceButton"> + <property name="text"> + <string>Issue money</string> </property> </widget> </item> @@ -45,5 +52,25 @@ </layout> </widget> <resources/> - <connections/> + <connections> + <connection> + <sender>issuanceButton</sender> + <signal>clicked()</signal> + <receiver>CommunityTabWidget</receiver> + <slot>openIssuanceDialog()</slot> + <hints> + <hint type="sourcelabel"> + <x>296</x> + <y>42</y> + </hint> + <hint type="destinationlabel"> + <x>199</x> + <y>149</y> + </hint> + </hints> + </connection> + </connections> + <slots> + <slot>openIssuanceDialog()</slot> + </slots> </ui> diff --git a/res/ui/issuanceDialog.ui b/res/ui/issuanceDialog.ui new file mode 100644 index 0000000000000000000000000000000000000000..f139380c9531b37940688afad7aaca273c51877e --- /dev/null +++ b/res/ui/issuanceDialog.ui @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>IssuanceDialog</class> + <widget class="QDialog" name="IssuanceDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="totalLabel"> + <property name="text"> + <string>Total : </string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>IssuanceDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>IssuanceDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/cutecoin/core/config.py b/src/cutecoin/core/config.py index 7efb38bdcdb0bde645eac878640a02dce1e26d61..177904f9144fc3a76555165ac7ac19e7a72eb117 100644 --- a/src/cutecoin/core/config.py +++ b/src/cutecoin/core/config.py @@ -7,6 +7,7 @@ Created on 7 févr. 2014 import logging from optparse import OptionParser import os.path +import gnupg home = os.path.expanduser("~") @@ -38,6 +39,9 @@ def parseArguments(argv): else: logging.getLogger().propagate = False + logger = logging.getLogger("gnupg") + logger.setLevel(logging.INFO) + parameters['home'] = options.home pass \ No newline at end of file diff --git a/src/cutecoin/gui/addAccountDialog.py b/src/cutecoin/gui/addAccountDialog.py index 9c5a6b7e476fb3edaeb2ce07868daf38689f9eec..32e04eee3d5cb88a2667fed1fefbf508421068ce 100644 --- a/src/cutecoin/gui/addAccountDialog.py +++ b/src/cutecoin/gui/addAccountDialog.py @@ -34,6 +34,7 @@ class AddAccountDialog(QDialog, Ui_AddAccountDialog): def setData(self): gpg = gnupg.GPG() + self.pgpKeysList.clear() availableKeys = gpg.list_keys(True) for key in availableKeys: self.pgpKeysList.addItem(key['uids'][0]) diff --git a/src/cutecoin/gui/communityTabWidget.py b/src/cutecoin/gui/communityTabWidget.py index 2b178c8f38da2d44ca5553c5a126b0ae764b107f..3925f9ea3079eba222587571d98122b8cc2aa044 100644 --- a/src/cutecoin/gui/communityTabWidget.py +++ b/src/cutecoin/gui/communityTabWidget.py @@ -3,9 +3,12 @@ Created on 2 févr. 2014 @author: inso ''' + +import logging from PyQt5.QtWidgets import QWidget from cutecoin.models.community.membersListModel import MembersListModel from cutecoin.models.transaction.issuancesListModel import IssuancesListModel +from cutecoin.gui.issuanceDialog import IssuanceDialog from cutecoin.gen_resources.communityTabWidget_uic import Ui_CommunityTabWidget class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): @@ -19,8 +22,17 @@ class CommunityTabWidget(QWidget, Ui_CommunityTabWidget): super(CommunityTabWidget, self).__init__() self.setupUi(self) self.community = community + self.account = account self.communityMembersList.setModel(MembersListModel(community)) self.issuancesList.setModel(IssuancesListModel(account, community)) + if self.account.issuedLastDividend(community): + self.issuanceButton.setEnabled(False) + else: + self.issuanceButton.setEnabled(True) + def openIssuanceDialog(self): + logging.debug("Display dialog") + issuanceDialog = IssuanceDialog(self.account, self.community) + issuanceDialog.exec_() diff --git a/src/cutecoin/gui/issuanceDialog.py b/src/cutecoin/gui/issuanceDialog.py new file mode 100644 index 0000000000000000000000000000000000000000..a786e9f00a3a8bb0a0b32ef5ab67f1f060b0f997 --- /dev/null +++ b/src/cutecoin/gui/issuanceDialog.py @@ -0,0 +1,88 @@ +''' +Created on 2 févr. 2014 + +@author: inso +''' +import logging +from math import pow + +from PyQt5.QtWidgets import QDialog, QVBoxLayout, QFrame, QSlider, QLabel, QDialogButtonBox +from PyQt5.QtCore import Qt, QSignalMapper + +from cutecoin.gen_resources.issuanceDialog_uic import Ui_IssuanceDialog + +class IssuanceDialog(QDialog, Ui_IssuanceDialog): + ''' + classdocs + ''' + + + def __init__(self, issuer, community): + ''' + Constructor + ''' + super(IssuanceDialog, self).__init__() + self.setupUi(self) + self.issuer = issuer + self.community = community + self.dividend = self.community.dividend() + self.coinMinimalPower = self.community.coinMinimalPower() + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + self.sliders = [] + self.slidersLabels = [] + maxCoinPower = 1 + nMax = 0 + # We look for the power of 10 which is directly higher than the dividend + while maxCoinPower < self.dividend: + maxCoinPower = pow(10, nMax) + nMax += 1 + + # N max is the power just before the one we found + + logging.debug("Pow max : " + str(nMax)) + + for i in range(self.coinMinimalPower, nMax): + self.generateSliderFrame(i) + + def generateSliderFrame(self, n): + frame = QFrame(self) + frame.setLayout(QVBoxLayout()) + + label = QLabel(frame) + frame.layout().addWidget(label) + + slider = QSlider(Qt.Horizontal, frame) + slider.setMinimum(0) + slider.setMaximum(9) + slider.valueChanged.connect(self.refreshTotal) + + frame.layout().addWidget(slider) + + label.setText("0 coins of " + str(pow(10, n))) + + + self.slidersLabels.append(label) + self.sliders.append(slider) + + self.layout().insertWidget(n, frame) + + def refreshTotal(self): + n = 0 + total = 0 + for slider in self.sliders: + self.slidersLabels[n].setText(str(slider.value()) + " coins of " + str(pow(10, n))) + self.totalLabel.setText("Total : " + str(total)) + total += slider.value()*pow(10, n) + n += 1 + self.totalLabel.setText("Total : " + str(total)) + if total != self.dividend: + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + else: + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True) + + + + + + diff --git a/src/cutecoin/gui/mainWindow.py b/src/cutecoin/gui/mainWindow.py index c11c4f098d1f77984bc3d2e14726c6b8329ef604..c56edffb9bd89b5afac3eb5aab3ff5897fb19263 100644 --- a/src/cutecoin/gui/mainWindow.py +++ b/src/cutecoin/gui/mainWindow.py @@ -78,6 +78,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.accountNameLabel.setText("Current account : " + self.core.currentAccount.name) self.walletsList.setModel(WalletsListModel(self.core.currentAccount)) self.walletContent.setModel(WalletListModel(self.core.currentAccount.wallets.walletsList[0])) + self.communitiesTab.clear() for community in self.core.currentAccount.communities.communitiesList: communityTab = CommunityTabWidget(self.core.currentAccount, community) self.communitiesTab.addTab(communityTab, community.name()) diff --git a/src/cutecoin/models/account/__init__.py b/src/cutecoin/models/account/__init__.py index 22bf3c8ce9a8b4d62baddd1f45fc61a749220e0b..9211f74ef94f3bfe61fcfb5b3e0b2903c60b9435 100644 --- a/src/cutecoin/models/account/__init__.py +++ b/src/cutecoin/models/account/__init__.py @@ -66,6 +66,20 @@ class Account(object): issuances.append(trxFactory.createTransaction(issuance['sender'], issuance['number'])) return issuances + def issuedLastDividend(self, community): + currentAmendmentNumber = community.amendmentNumber() + + if community in self.communities.communitiesList: + dividendsData = community.ucoinRequest(ucoin.hdc.transactions.sender.issuance.Dividend(self.keyFingerprint(), currentAmendmentNumber)) + for dividend in dividendsData: + # Small bug in ucoinpy library + if not isinstance(dividend, str): + return True + + return False + + + def jsonify(self): data = {'name' : self.name, 'pgpKeyId' : self.pgpKeyId, diff --git a/src/cutecoin/models/account/factory.py b/src/cutecoin/models/account/factory.py index ba2c91fd1026ed48601ea773e8ff4f340fea42b8..85971dfcab42194aab771f24fe8b6f1447513c8d 100644 --- a/src/cutecoin/models/account/factory.py +++ b/src/cutecoin/models/account/factory.py @@ -3,12 +3,16 @@ Created on 11 févr. 2014 @author: inso ''' + +import logging + from cutecoin.models.account import Account from cutecoin.models.account.wallets import Wallets from cutecoin.models.account.communities import Communities from cutecoin.models.wallet import factory as walletFactory from cutecoin.models.community import factory as communityFactory + def createAccount(pgpKeyId, name, communities): ''' Constructor @@ -29,7 +33,6 @@ def loadAccount(jsonData): account.name = jsonData['name'] account.communities = Communities() account.wallets = Wallets() - for communityData in jsonData['communities']: account.communities.communitiesList.append(communityFactory.loadCommunity(communityData, account)) return account \ No newline at end of file diff --git a/src/cutecoin/models/community/__init__.py b/src/cutecoin/models/community/__init__.py index 2825d08f6016f95d5256c80b52e9360b4cc92a50..98b37e21b360cae0b90faae402da5c6c61b9e749 100644 --- a/src/cutecoin/models/community/__init__.py +++ b/src/cutecoin/models/community/__init__.py @@ -61,6 +61,21 @@ class Community(object): def name(self): return self.currency + def dividend(self): + currentAmendment = self.ucoinRequest(ucoin.hdc.amendments.Current()) + return currentAmendment['dividend'] + + def coinMinimalPower(self): + currentAmendment = self.ucoinRequest(ucoin.hdc.amendments.Current()) + if 'coinMinimalPower' in currentAmendment.keys(): + return currentAmendment['coinMinimalPower'] + else: + return 0 + + def amendmentNumber(self): + currentAmendment = self.ucoinRequest(ucoin.hdc.amendments.Current()) + return currentAmendment['number'] + def jsonifyNodesList(self): data = [] for node in self.knownNodes: diff --git a/src/cutecoin/models/transaction/__init__.py b/src/cutecoin/models/transaction/__init__.py index 171cf52687f498b46ae8871cdd149d26227c21e3..cec0028be0bfb97fa80b8b275ec47dc4bf47a033 100644 --- a/src/cutecoin/models/transaction/__init__.py +++ b/src/cutecoin/models/transaction/__init__.py @@ -56,6 +56,9 @@ class Issuance(Transaction): ''' def __init__(self): super(Issuance).__init__() + + def amendmentNumber(self): + self.community.ucoinRequest(ucoin.hdc.transactions.View(self.sender.pgpFingerprint + "-" + self.increment)) def getText(self): return str(self.value) + " " + self.currency