PIVX Core  5.6.99
P2P Digital Currency
createproposaldialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021 The PIVX Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or https://www.opensource.org/licenses/mit-license.php.
4 
5 #include "createproposaldialog.h"
6 #include "ui_createproposaldialog.h"
7 
8 #include "contactsdropdown.h"
9 #include "governancemodel.h"
10 #include "pwidget.h"
11 #include "qtutils.h"
12 #include "snackbar.h"
13 
14 void initPageIndexBtn(QPushButton* btn)
15 {
16  QSize BUTTON_SIZE = QSize(22, 22);
17  setCssProperty(btn, "ic-step-confirm");
18  btn->setMinimumSize(BUTTON_SIZE);
19  btn->setMaximumSize(BUTTON_SIZE);
20  btn->move(0, 0);
21  btn->show();
22  btn->raise();
23  btn->setVisible(false);
24 }
25 
27  QDialog(parent),
28  ui(new Ui::CreateProposalDialog),
29  govModel(_govModel),
30  walletModel(_walletModel),
31  icConfirm1(new QPushButton(this)),
32  icConfirm2(new QPushButton(this)),
33  icConfirm3(new QPushButton(this))
34 {
35  ui->setupUi(this);
36  this->setStyleSheet(parent->styleSheet());
37 
38  setCssProperty(ui->frame, "container-dialog");
39  ui->frame->setContentsMargins(10,10,10,10);
40  setCssProperty({ui->labelLine1, ui->labelLine2}, "line-purple");
41  setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border");
42  setCssProperty({ui->pushNumber1, ui->pushNumber2, ui->pushNumber3}, "btn-number-check");
43  setCssProperty({ui->pushName1, ui->pushName2, ui->pushName3}, "btn-name-check");
44 
45  // Pages setup
46  setupPageOne();
47  setupPageTwo();
49 
50  // Confirm icons
51  ui->stackedIcon1->addWidget(icConfirm1);
52  ui->stackedIcon2->addWidget(icConfirm2);
53  ui->stackedIcon3->addWidget(icConfirm3);
57 
58  // Connect btns
59  setCssProperty({ui->btnNext, ui->btnGenAddr}, "btn-primary");
60  ui->btnGenAddr->setVisible(false);
61  ui->btnGenAddr->setText(tr("GENERATE ADDRESS"));
62  ui->btnNext->setText(tr("NEXT"));
63  setCssProperty(ui->btnBack, "btn-dialog-cancel");
64  ui->btnBack->setVisible(false);
65  ui->btnBack->setText(tr("BACK"));
66  setCssProperty(ui->pushButtonSkip, "ic-close");
67 
68  connect(ui->pushButtonSkip, &QPushButton::clicked, this, &CreateProposalDialog::close);
69  connect(ui->btnNext, &QPushButton::clicked, this, &CreateProposalDialog::onNextClicked);
70  connect(ui->btnBack, &QPushButton::clicked, this, &CreateProposalDialog::onBackClicked);
71  connect(ui->btnGenAddr, &QPushButton::clicked, this, &CreateProposalDialog::onGenAddressClicked);
72 }
73 
74 void setEditBoxStyle(QLabel* label, QLineEdit* lineEdit, const QString& placeholderText)
75 {
76  setCssProperty(label, "text-title");
77  lineEdit->setPlaceholderText(placeholderText);
78  setCssProperty(lineEdit, "edit-primary");
79  lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
80  setShadow(lineEdit);
81 }
82 
84 {
85  setCssProperty(ui->labelTitle1, "text-title-dialog");
86  setCssProperty(ui->labelMessage1b, "dialog-proposal-message");
87  setEditBoxStyle(ui->labelName, ui->lineEditPropName, "e.g Best proposal ever!");
88  setEditBoxStyle(ui->labelURL, ui->lineEditURL, "e.g https://forum.pivx/proposals/best_proposal_ever");
89 
90  connect(ui->lineEditPropName, &QLineEdit::textChanged, this, &CreateProposalDialog::propNameChanged);
91  connect(ui->lineEditURL, &QLineEdit::textChanged, this, &CreateProposalDialog::propUrlChanged);
92 }
93 
95 {
96  setCssProperty(ui->labelTitleDest, "text-title-dialog");
97  setCssProperty(ui->labelMessageDest, "dialog-proposal-message");
98  setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV");
99  setCssProperty(ui->labelMonths, "text-title");
100  setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something..");
101  setCssProperty(ui->lineEditAddress, "edit-primary-multi-book");
102  actAddrList = ui->lineEditAddress->addAction(QIcon("://ic-contact-arrow-down"), QLineEdit::TrailingPosition);
103  GUIUtil::setupAmountWidget(ui->lineEditAmount, this);
104  setCssProperty(ui->lineEditMonths, "btn-spin-box");
105  setShadow(ui->lineEditMonths);
106  ui->lineEditMonths->setAttribute(Qt::WA_MacShowFocusRect, false);
107  connect(ui->lineEditMonths, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
108  &CreateProposalDialog::monthsEditDeselect, Qt::QueuedConnection);
109  connect(ui->lineEditMonths->findChild<QLineEdit*>(), &QLineEdit::cursorPositionChanged,
110  this, &CreateProposalDialog::monthsEditDeselect, Qt::QueuedConnection);
111 
112  connect(ui->lineEditAmount, &QLineEdit::textChanged, this, &CreateProposalDialog::propAmountChanged);
113  connect(ui->lineEditAddress, &QLineEdit::textChanged, this, &CreateProposalDialog::propaddressChanged);
114  connect(actAddrList, &QAction::triggered, this, &CreateProposalDialog::onAddrListClicked);
115 }
116 
118 {
119  ui->lineEditMonths->findChild<QLineEdit*>()->deselect();
120  ui->lineEditMonths->clearFocus();
121 }
122 
124 {
125  setCssProperty(ui->labelTitle3, "text-title-dialog");
126  ui->stackedWidget->setCurrentIndex(pos);
127  setCssProperty({ui->labelResultNameTitle,
128  ui->labelResultAmountTitle,
129  ui->labelResultAddressTitle,
130  ui->labelResultMonthsTitle,
131  ui->labelResultUrlTitle},
132  "text-title");
133  setCssProperty({ui->labelResultName,
134  ui->labelResultName,
135  ui->labelResultAmount,
136  ui->labelResultAddress,
137  ui->labelResultMonths,
138  ui->labelResultUrl}, "text-body1-dialog");
139 }
140 
141 void CreateProposalDialog::propNameChanged(const QString& newText)
142 {
143  bool isValid = !newText.isEmpty() && IsValidUTF8(newText.toStdString()) && govModel->validatePropName(newText).getRes();
144  setCssEditLine(ui->lineEditPropName, isValid, true);
145 }
146 
147 void CreateProposalDialog::propUrlChanged(const QString& newText)
148 {
149  setCssEditLine(ui->lineEditURL, govModel->validatePropURL(newText).getRes(), true);
150 }
151 
152 void CreateProposalDialog::propAmountChanged(const QString& newText)
153 {
154  if (newText.isEmpty()) {
155  setCssEditLine(ui->lineEditAmount, true, true);
156  return;
157  }
158  auto amount = GUIUtil::parseValue(newText);
159  if (amount > govModel->getMaxAvailableBudgetAmount()) {
160  ui->lineEditAmount->setText(newText.left(newText.size() - 1));
161  return;
162  }
163  setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(amount).getRes(), true);
164 }
165 
167 {
168  if (!str.isEmpty()) {
169  QString trimmedStr = str.trimmed();
170  bool isShielded = false;
171  const bool valid = walletModel->validateAddress(trimmedStr, false, isShielded) && !isShielded;
172  setCssEditLine(ui->lineEditAddress, valid, true);
173  return valid;
174  }
175  setCssEditLine(ui->lineEditAddress, true, true);
176  return false;
177 }
178 
180 {
181  QString propName = ui->lineEditPropName->text();
182  if (propName.isEmpty()) {
183  setCssEditLine(ui->lineEditPropName, false, true);
184  inform(tr("Proposal name cannot be empty"));
185  return false;
186  }
187 
188  auto res = govModel->validatePropName(propName);
189  if (!res) {
190  inform(QString::fromStdString(res.getError()));
191  return false;
192  }
193 
194  // For now, only accept UTF8 valid strings.
195  if (!IsValidUTF8(propName.toStdString())) {
196  setCssEditLine(ui->lineEditPropName, false, true);
197  inform(tr("Proposal name cannot contain non UTF-8 characters"));
198  return false;
199  }
200 
201  res = govModel->validatePropURL(ui->lineEditURL->text());
202  if (!res) inform(QString::fromStdString(res.getError()));
203  return res.getRes();
204 }
205 
207 {
208  // Amount validation
209  auto amount = GUIUtil::parseValue(ui->lineEditAmount->text());
210  if (amount <= 0) {
211  inform(tr("Invalid amount"));
212  return false;
213  }
214  auto opRes = govModel->validatePropAmount(amount);
215  if (!opRes) {
216  inform(QString::fromStdString(opRes.getError()));
217  return false;
218  }
219 
220  // Payments count validation
221  opRes = govModel->validatePropPaymentCount(ui->lineEditMonths->value());
222  if (!opRes) {
223  inform(QString::fromStdString(opRes.getError()));
224  return false;
225  }
226 
227  if (!propaddressChanged(ui->lineEditAddress->text())) {
228  inform(tr("Invalid payment address"));
229  return false;
230  }
231 
232  return true;
233 }
234 
236 {
237  ui->labelResultName->setText(ui->lineEditPropName->text());
238  ui->labelResultUrl->setText(ui->lineEditURL->text());
239  ui->labelResultAmount->setText(GUIUtil::formatBalance(GUIUtil::parseValue(ui->lineEditAmount->text())));
240  ui->labelResultMonths->setText(QString::number(ui->lineEditMonths->value()));
241  ui->labelResultAddress->setText(ui->lineEditAddress->text());
242  ui->labelResultUrl->setText(ui->lineEditURL->text());
243 }
244 
246 {
247  int months = ui->lineEditMonths->value();
248  CAmount amount = GUIUtil::parseValue(ui->lineEditAmount->text());
249  auto opRes = govModel->createProposal(
250  ui->lineEditPropName->text().toStdString(),
251  ui->lineEditURL->text().toStdString(),
252  months,
253  amount,
254  ui->lineEditAddress->text().toStdString()
255  );
256  if (!opRes) {
257  inform(QString::fromStdString(opRes.getError()));
258  return;
259  }
260  accept();
261 }
262 
264 {
265  int nextPos = pos + 1;
266  switch (pos) {
267  case 0: {
268  if (!validatePageOne()) return;
269  ui->stackedWidget->setCurrentIndex(nextPos);
270  ui->pushNumber2->setChecked(true);
271  ui->pushName3->setChecked(false);
272  ui->pushName2->setChecked(true);
273  ui->pushName1->setChecked(true);
274  icConfirm1->setVisible(true);
275  ui->btnBack->setVisible(true);
276  ui->btnGenAddr->setVisible(true);
277  break;
278  }
279  case 1: {
280  if (!validatePageTwo()) return;
281  loadSummary();
282  ui->stackedWidget->setCurrentIndex(nextPos);
283  ui->pushNumber3->setChecked(true);
284  ui->pushName3->setChecked(true);
285  ui->pushName2->setChecked(true);
286  ui->pushName1->setChecked(true);
287  icConfirm2->setVisible(true);
288  ui->btnNext->setText(tr("Send"));
289  break;
290  }
291  case 2: {
292  sendProposal();
293  }
294  }
295  pos = nextPos;
296 }
297 
299 {
300  if (pos == 0) return;
301  pos--;
302  switch(pos) {
303  case 0: {
304  ui->stackedWidget->setCurrentIndex(pos);
305  ui->pushNumber1->setChecked(true);
306  ui->pushNumber3->setChecked(false);
307  ui->pushNumber2->setChecked(false);
308  ui->pushName3->setChecked(false);
309  ui->pushName2->setChecked(false);
310  ui->pushName1->setChecked(true);
311  icConfirm1->setVisible(false);
312  ui->btnBack->setVisible(false);
313  ui->btnGenAddr->setVisible(false);
314  break;
315  }
316  case 1: {
317  ui->stackedWidget->setCurrentIndex(pos);
318  ui->pushNumber2->setChecked(true);
319  ui->pushNumber3->setChecked(false);
320  ui->pushName3->setChecked(false);
321  ui->pushName2->setChecked(true);
322  ui->pushName1->setChecked(true);
323  icConfirm2->setVisible(false);
324  ui->btnNext->setText(tr("Next"));
325  break;
326  }
327  }
328 }
329 
331 {
332  int addrSize = walletModel->getAddressTableModel()->sizeSend() +
334  if (addrSize == 0) {
335  inform(tr("No contacts available, you can go to the contacts screen and add some there!"));
336  return;
337  }
338 
339  int rowHeight = ui->lineEditAddress->height();
340  int height = 70 * 2 + 1; // 2 rows (70 each row).
341  int width = ui->lineEditAddress->width();
342 
343  if (!menuContacts) {
344  // TODO: add different row icon for contacts and own addresses.
345  // TODO: add filter/search option.
346  // TODO: fix bug that the last presented address isn't being showed.
348  width,
349  height,
350  dynamic_cast<PIVXGUI*>(parent()),
351  this
352  );
354  connect(menuContacts, &ContactsDropdown::contactSelected, [this](const QString& address, const QString& label) {
355  ui->lineEditAddress->setText(address);
356  });
357 
358  }
359 
360  if (menuContacts->isVisible()) {
361  menuContacts->hide();
362  return;
363  }
364 
365  menuContacts->resizeList(width, height);
366  menuContacts->setStyleSheet(this->styleSheet());
367  menuContacts->adjustSize();
368 
369  QPoint position = ui->containerPage2->rect().bottomLeft();
370  position.setY(position.y() + rowHeight * 2 - 20);
371  position.setX(position.x() + 74); // Add widget's fixed padding manually
372  menuContacts->move(position);
373  menuContacts->show();
374 }
375 
377 {
378  std::string addrLabel = ui->lineEditPropName->text().toStdString();
379  CallResult<Destination> addr = !addrLabel.empty() ? walletModel->getNewAddress(addrLabel) : walletModel->getNewAddress("");
380  QString newAddr = QString::fromStdString(addr.getObjResult()->ToString());
381  ui->lineEditAddress->setText(newAddr);
382 }
383 
385 {
386  if (e->type() == QEvent::KeyPress) {
387  QKeyEvent* ke = static_cast<QKeyEvent*>(e);
388  if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) onNextClicked();
389  if (ke->key() == Qt::Key_Escape) reject();
390  }
391 }
392 
393 void CreateProposalDialog::showEvent(QShowEvent *event)
394 {
395  switch (pos) {
396  case 0:
397  if (ui->lineEditPropName) ui->lineEditPropName->setFocus();
398  break;
399  case 1:
400  if (ui->lineEditAddress) ui->lineEditAddress->setFocus();
401  break;
402  case 2:
403  if (ui->btnNext) ui->btnNext->setFocus();
404  break;
405  default:
406  return;
407  }
408 }
409 
410 void CreateProposalDialog::inform(const QString& text)
411 {
412  if (!snackBar) snackBar = new SnackBar(nullptr, this);
413  snackBar->setText(text);
414  snackBar->resize(this->width(), snackBar->height());
415  openDialog(snackBar, this);
416 }
417 
419 {
420  delete ui;
421 }
int64_t CAmount
Amount in PIV (Can be negative)
Definition: amount.h:13
static const QString Send
Specifies send address.
static const QString Receive
Specifies receive address.
const Optional< T > & getObjResult() const
void resizeList(int minWidth, int mintHeight)
void contactSelected(QString address, QString label)
void setWalletModel(WalletModel *_model, const QStringList &type)
void showEvent(QShowEvent *e) override
Ui::CreateProposalDialog * ui
void propUrlChanged(const QString &newText)
void propAmountChanged(const QString &newText)
bool propaddressChanged(const QString &newText)
GovernanceModel * govModel
void propNameChanged(const QString &newText)
CreateProposalDialog(PIVXGUI *parent, GovernanceModel *_govModel, WalletModel *_walletModel)
void keyPressEvent(QKeyEvent *e) override
void inform(const QString &text)
ContactsDropdown * menuContacts
OperationResult validatePropPaymentCount(int paymentCount) const
OperationResult validatePropURL(const QString &url) const
OperationResult validatePropName(const QString &name) const
OperationResult validatePropAmount(CAmount amount) const
OperationResult createProposal(const std::string &strProposalName, const std::string &strURL, int nPaymentCount, CAmount nAmount, const std::string &strPaymentAddr)
CAmount getMaxAvailableBudgetAmount() const
bool getRes() const
PIVX GUI main class.
Definition: pivxgui.h:46
void setText(const QString &text)
Definition: snackbar.cpp:51
Interface to PIVX wallet from Qt view code.
Definition: walletmodel.h:109
bool validateAddress(const QString &address)
CallResult< Destination > getNewAddress(const std::string &label="") const
AddressTableModel * getAddressTableModel()
void setEditBoxStyle(QLabel *label, QLineEdit *lineEdit, const QString &placeholderText)
void initPageIndexBtn(QPushButton *btn)
void setupAmountWidget(QLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:141
QString formatBalance(CAmount amount, int nDisplayUnit, bool isZpiv)
Definition: guiutil.cpp:119
void setShadow(QWidget *edit)
Definition: qtutils.cpp:292
void setCssProperty(std::initializer_list< QWidget * > args, const QString &value)
Definition: qtutils.cpp:334
bool openDialog(QDialog *widget, QWidget *gui)
Definition: qtutils.cpp:23
void setCssEditLine(QLineEdit *edit, bool isValid, bool forceUpdate)
Definition: qtutils.cpp:282
bool IsValidUTF8(const std::string &str)
Checks for valid 4-byte UTF-8 encoding in a string.