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