Remove boost foreach macro
[novacoin.git] / src / qt / addresstablemodel.cpp
1 #include "addresstablemodel.h"
2 #include "guiutil.h"
3 #include "walletmodel.h"
4
5 #include "wallet.h"
6 #include "base58.h"
7
8 #include <QFont>
9 #include <QColor>
10
11 const QString AddressTableModel::Send = "S";
12 const QString AddressTableModel::Receive = "R";
13
14 struct AddressTableEntry
15 {
16     enum Type {
17         Sending,
18         Receiving
19     };
20
21     Type type;
22     QString label;
23     QString address;
24
25     AddressTableEntry() {}
26     AddressTableEntry(Type type, const QString &label, const QString &address):
27         type(type), label(label), address(address) {}
28 };
29
30 struct AddressTableEntryLessThan
31 {
32     bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const
33     {
34         return a.address < b.address;
35     }
36     bool operator()(const AddressTableEntry &a, const QString &b) const
37     {
38         return a.address < b;
39     }
40     bool operator()(const QString &a, const AddressTableEntry &b) const
41     {
42         return a < b.address;
43     }
44 };
45
46 // Private implementation
47 class AddressTablePriv
48 {
49 public:
50     CWallet *wallet;
51     QList<AddressTableEntry> cachedAddressTable;
52     AddressTableModel *parent;
53
54     AddressTablePriv(CWallet *wallet, AddressTableModel *parent):
55         wallet(wallet), parent(parent) {}
56
57     void refreshAddressTable()
58     {
59         cachedAddressTable.clear();
60         {
61             LOCK(wallet->cs_wallet);
62             for (const auto& item : wallet->mapAddressBook)
63             {
64                 const CBitcoinAddress& address = item.first;
65                 const std::string& strName = item.second;
66                 bool fMine = IsMine(*wallet, address);
67                 cachedAddressTable.append(AddressTableEntry(fMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending,
68                                   QString::fromStdString(strName),
69                                   QString::fromStdString(address.ToString())));
70             }
71         }
72         // qLowerBound() and qUpperBound() require our cachedAddressTable list to be sorted in asc order
73         qSort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan());
74     }
75
76     void updateEntry(const QString &address, const QString &label, bool isMine, int status)
77     {
78         // Find address / label in model
79         QList<AddressTableEntry>::iterator lower = qLowerBound(
80             cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
81         QList<AddressTableEntry>::iterator upper = qUpperBound(
82             cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan());
83         int lowerIndex = (lower - cachedAddressTable.begin());
84         int upperIndex = (upper - cachedAddressTable.begin());
85         bool inModel = (lower != upper);
86         AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending;
87
88         switch(status)
89         {
90         case CT_NEW:
91             if(inModel)
92             {
93                 OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n");
94                 break;
95             }
96             parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex);
97             cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address));
98             parent->endInsertRows();
99             break;
100         case CT_UPDATED:
101             if(!inModel)
102             {
103                 OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n");
104                 break;
105             }
106             lower->type = newEntryType;
107             lower->label = label;
108             parent->emitDataChanged(lowerIndex);
109             break;
110         case CT_DELETED:
111             if(!inModel)
112             {
113                 OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n");
114                 break;
115             }
116             parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
117             cachedAddressTable.erase(lower, upper);
118             parent->endRemoveRows();
119             break;
120         }
121     }
122
123     int size()
124     {
125         return cachedAddressTable.size();
126     }
127
128     AddressTableEntry *index(int idx)
129     {
130         if(idx >= 0 && idx < cachedAddressTable.size())
131         {
132             return &cachedAddressTable[idx];
133         }
134         else
135         {
136             return 0;
137         }
138     }
139 };
140
141 AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) :
142     QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0)
143 {
144     columns << tr("Label") << tr("Address");
145     priv = new AddressTablePriv(wallet, this);
146     priv->refreshAddressTable();
147 }
148
149 AddressTableModel::~AddressTableModel()
150 {
151     delete priv;
152 }
153
154 int AddressTableModel::rowCount(const QModelIndex &parent) const
155 {
156     Q_UNUSED(parent);
157     return priv->size();
158 }
159
160 int AddressTableModel::columnCount(const QModelIndex &parent) const
161 {
162     Q_UNUSED(parent);
163     return columns.length();
164 }
165
166 QVariant AddressTableModel::data(const QModelIndex &index, int role) const
167 {
168     if(!index.isValid())
169         return QVariant();
170
171     AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
172
173     if(role == Qt::DisplayRole || role == Qt::EditRole)
174     {
175         switch(index.column())
176         {
177         case Label:
178             if(rec->label.isEmpty() && role == Qt::DisplayRole)
179             {
180                 return tr("(no label)");
181             }
182             else
183             {
184                 return rec->label;
185             }
186         case Address:
187             return rec->address;
188         }
189     }
190     else if (role == Qt::FontRole)
191     {
192         QFont font;
193         if(index.column() == Address)
194         {
195             font = GUIUtil::bitcoinAddressFont();
196         }
197         return font;
198     }
199     else if (role == TypeRole)
200     {
201         switch(rec->type)
202         {
203         case AddressTableEntry::Sending:
204             return Send;
205         case AddressTableEntry::Receiving:
206             return Receive;
207         default: break;
208         }
209     }
210     return QVariant();
211 }
212
213 bool AddressTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
214 {
215     if(!index.isValid())
216         return false;
217     AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
218
219     editStatus = OK;
220
221     if(role == Qt::EditRole)
222     {
223         switch(index.column())
224         {
225         case Label:
226             // Do nothing, if old label == new label
227             if(rec->label == value.toString())
228             {
229                 editStatus = NO_CHANGES;
230                 return false;
231             }
232             wallet->SetAddressBookName(CBitcoinAddress(rec->address.toStdString()), value.toString().toStdString());
233             break;
234         case Address:
235             // Do nothing, if old address == new address
236             if(CBitcoinAddress(rec->address.toStdString()) == CBitcoinAddress(value.toString().toStdString()))
237             {
238                 editStatus = NO_CHANGES;
239                 return false;
240             }
241             // Refuse to set invalid address, set error status and return false
242             else if(!walletModel->validateAddress(value.toString()))
243             {
244                 editStatus = INVALID_ADDRESS;
245                 return false;
246             }
247             // Check for duplicate addresses to prevent accidental deletion of addresses, if you try
248             // to paste an existing address over another address (with a different label)
249             else if(wallet->mapAddressBook.count(CBitcoinAddress(value.toString().toStdString())))
250             {
251                 editStatus = DUPLICATE_ADDRESS;
252                 return false;
253             }
254             // Double-check that we're not overwriting a receiving address
255             else if(rec->type == AddressTableEntry::Sending)
256             {
257                 {
258                     LOCK(wallet->cs_wallet);
259                     // Remove old entry
260                     wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()));
261                     // Add new entry with new address
262                     wallet->SetAddressBookName(CBitcoinAddress(value.toString().toStdString()), rec->label.toStdString());
263                 }
264             }
265             break;
266         }
267         return true;
268     }
269     return false;
270 }
271
272 QVariant AddressTableModel::headerData(int section, Qt::Orientation orientation, int role) const
273 {
274     if(orientation == Qt::Horizontal)
275     {
276         if(role == Qt::DisplayRole)
277         {
278             return columns[section];
279         }
280     }
281     return QVariant();
282 }
283
284 Qt::ItemFlags AddressTableModel::flags(const QModelIndex &index) const
285 {
286     if(!index.isValid())
287         return 0;
288     AddressTableEntry *rec = static_cast<AddressTableEntry*>(index.internalPointer());
289
290     Qt::ItemFlags retval = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
291     // Can edit address and label for sending addresses,
292     // and only label for receiving addresses.
293     if(rec->type == AddressTableEntry::Sending ||
294       (rec->type == AddressTableEntry::Receiving && index.column()==Label))
295     {
296         retval |= Qt::ItemIsEditable;
297     }
298     return retval;
299 }
300
301 QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &parent) const
302 {
303     Q_UNUSED(parent);
304     AddressTableEntry *data = priv->index(row);
305     if(data)
306     {
307         return createIndex(row, column, priv->index(row));
308     }
309     else
310     {
311         return QModelIndex();
312     }
313 }
314
315 void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status)
316 {
317     // Update address book model from Bitcoin core
318     priv->updateEntry(address, label, isMine, status);
319 }
320
321 QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address)
322 {
323     std::string strLabel = label.toStdString();
324     std::string strAddress = address.toStdString();
325
326     editStatus = OK;
327
328     if(type == Send)
329     {
330         if(!walletModel->validateAddress(address))
331         {
332             editStatus = INVALID_ADDRESS;
333             return QString();
334         }
335         // Check for duplicate addresses
336         {
337             LOCK(wallet->cs_wallet);
338             if(wallet->mapAddressBook.count(CBitcoinAddress(strAddress)))
339             {
340                 editStatus = DUPLICATE_ADDRESS;
341                 return QString();
342             }
343         }
344     }
345     else if(type == Receive)
346     {
347         // Generate a new address to associate with given label
348         WalletModel::UnlockContext ctx(walletModel->requestUnlock());
349         if(!ctx.isValid())
350         {
351             // Unlock wallet failed or was cancelled
352             editStatus = WALLET_UNLOCK_FAILURE;
353             return QString();
354         }
355         CPubKey newKey;
356         if(!wallet->GetKeyFromPool(newKey, true))
357         {
358             editStatus = KEY_GENERATION_FAILURE;
359             return QString();
360         }
361         strAddress = CBitcoinAddress(newKey.GetID()).ToString();
362     }
363     else
364     {
365         return QString();
366     }
367
368     // Add entry
369     {
370         LOCK(wallet->cs_wallet);
371         wallet->SetAddressBookName(CBitcoinAddress(strAddress), strLabel);
372     }
373     return QString::fromStdString(strAddress);
374 }
375
376 bool AddressTableModel::removeRows(int row, int count, const QModelIndex &parent)
377 {
378     Q_UNUSED(parent);
379     AddressTableEntry *rec = priv->index(row);
380     if(count != 1 || !rec || rec->type == AddressTableEntry::Receiving)
381     {
382         // Can only remove one row at a time, and cannot remove rows not in model.
383         // Also refuse to remove receiving addresses.
384         return false;
385     }
386     {
387         LOCK(wallet->cs_wallet);
388         wallet->DelAddressBookName(CBitcoinAddress(rec->address.toStdString()));
389     }
390     return true;
391 }
392
393 /* Look up label for address in address book, if not found return empty string.
394  */
395 QString AddressTableModel::labelForAddress(const QString &address) const
396 {
397     {
398         LOCK(wallet->cs_wallet);
399         CBitcoinAddress address_parsed(address.toStdString());
400         std::map<CBitcoinAddress, std::string>::iterator mi = wallet->mapAddressBook.find(address_parsed);
401         if (mi != wallet->mapAddressBook.end())
402         {
403             return QString::fromStdString(mi->second);
404         }
405     }
406     return QString();
407 }
408
409 int AddressTableModel::lookupAddress(const QString &address) const
410 {
411     QModelIndexList lst = match(index(0, Address, QModelIndex()),
412                                 Qt::EditRole, address, 1, Qt::MatchExactly);
413     if(lst.isEmpty())
414     {
415         return -1;
416     }
417     else
418     {
419         return lst.at(0).row();
420     }
421 }
422
423 void AddressTableModel::emitDataChanged(int idx)
424 {
425     emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex()));
426 }