1 // Copyright (c) 2009-2010 Satoshi Nakamoto
2 // Distributed under the MIT/X11 software license, see the accompanying
3 // file license.txt or http://www.opensource.org/licenses/mit-license.php.
12 DEFINE_EVENT_TYPE(wxEVT_UITHREADCALL)
14 CMainFrame* pframeMain = NULL;
15 CMyTaskBarIcon* ptaskbaricon = NULL;
16 bool fClosedToTray = false;
26 //////////////////////////////////////////////////////////////////////////////
31 void HandleCtrlA(wxKeyEvent& event)
35 wxTextCtrl* textCtrl = (wxTextCtrl*)event.GetEventObject();
36 if (event.GetModifiers() == wxMOD_CONTROL && event.GetKeyCode() == 'A')
37 textCtrl->SetSelection(-1, -1);
42 //char pszHourFormat[256];
43 //pszHourFormat[0] = '\0';
44 //GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, pszHourFormat, 256);
45 //return (pszHourFormat[0] != '0');
49 string DateStr(int64 nTime)
51 // Can only be used safely here in the UI
52 return (string)wxDateTime((time_t)nTime).FormatDate();
55 string DateTimeStr(int64 nTime)
57 // Can only be used safely here in the UI
58 wxDateTime datetime((time_t)nTime);
60 return (string)datetime.Format("%x %H:%M");
62 return (string)datetime.Format("%x ") + itostr((datetime.GetHour() + 11) % 12 + 1) + (string)datetime.Format(":%M %p");
65 wxString GetItemText(wxListCtrl* listCtrl, int nIndex, int nColumn)
67 // Helper to simplify access to listctrl
69 item.m_itemId = nIndex;
71 item.m_mask = wxLIST_MASK_TEXT;
72 if (!listCtrl->GetItem(item))
74 return item.GetText();
77 int InsertLine(wxListCtrl* listCtrl, const wxString& str0, const wxString& str1)
79 int nIndex = listCtrl->InsertItem(listCtrl->GetItemCount(), str0);
80 listCtrl->SetItem(nIndex, 1, str1);
84 int InsertLine(wxListCtrl* listCtrl, const wxString& str0, const wxString& str1, const wxString& str2, const wxString& str3, const wxString& str4)
86 int nIndex = listCtrl->InsertItem(listCtrl->GetItemCount(), str0);
87 listCtrl->SetItem(nIndex, 1, str1);
88 listCtrl->SetItem(nIndex, 2, str2);
89 listCtrl->SetItem(nIndex, 3, str3);
90 listCtrl->SetItem(nIndex, 4, str4);
94 int InsertLine(wxListCtrl* listCtrl, void* pdata, const wxString& str0, const wxString& str1, const wxString& str2, const wxString& str3, const wxString& str4)
96 int nIndex = listCtrl->InsertItem(listCtrl->GetItemCount(), str0);
97 listCtrl->SetItemPtrData(nIndex, (wxUIntPtr)pdata);
98 listCtrl->SetItem(nIndex, 1, str1);
99 listCtrl->SetItem(nIndex, 2, str2);
100 listCtrl->SetItem(nIndex, 3, str3);
101 listCtrl->SetItem(nIndex, 4, str4);
105 void SetSelection(wxListCtrl* listCtrl, int nIndex)
107 int nSize = listCtrl->GetItemCount();
108 long nState = (wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
109 for (int i = 0; i < nSize; i++)
110 listCtrl->SetItemState(i, (i == nIndex ? nState : 0), nState);
113 int GetSelection(wxListCtrl* listCtrl)
115 int nSize = listCtrl->GetItemCount();
116 for (int i = 0; i < nSize; i++)
117 if (listCtrl->GetItemState(i, wxLIST_STATE_FOCUSED))
122 string HtmlEscape(const char* psz, bool fMultiLine=false)
125 for (const char* p = psz; *p; p++)
127 if (*p == '<') len += 4;
128 else if (*p == '>') len += 4;
129 else if (*p == '&') len += 5;
130 else if (*p == '"') len += 6;
131 else if (*p == ' ' && p > psz && p[-1] == ' ' && p[1] == ' ') len += 6;
132 else if (*p == '\n' && fMultiLine) len += 5;
138 for (const char* p = psz; *p; p++)
140 if (*p == '<') str += "<";
141 else if (*p == '>') str += ">";
142 else if (*p == '&') str += "&";
143 else if (*p == '"') str += """;
144 else if (*p == ' ' && p > psz && p[-1] == ' ' && p[1] == ' ') str += " ";
145 else if (*p == '\n' && fMultiLine) str += "<br>\n";
152 string HtmlEscape(const string& str, bool fMultiLine=false)
154 return HtmlEscape(str.c_str(), fMultiLine);
157 void CalledMessageBox(const string& message, const string& caption, int style, wxWindow* parent, int x, int y, int* pnRet, bool* pfDone)
159 *pnRet = wxMessageBox(message, caption, style, parent, x, y);
163 int ThreadSafeMessageBox(const string& message, const string& caption, int style, wxWindow* parent, int x, int y)
166 return wxMessageBox(message, caption, style, parent, x, y);
168 if (wxThread::IsMain() || fDaemon)
170 return wxMessageBox(message, caption, style, parent, x, y);
176 UIThreadCall(bind(CalledMessageBox, message, caption, style, parent, x, y, &nRet, &fDone));
184 bool ThreadSafeAskFee(int64 nFeeRequired, const string& strCaption, wxWindow* parent)
186 if (nFeeRequired == 0 || fDaemon)
188 string strMessage = strprintf(
189 _("This transaction is over the size limit. You can still send it for a fee of %s, "
190 "which goes to the nodes that process your transaction and helps to support the network. "
191 "Do you want to pay the fee?"),
192 FormatMoney(nFeeRequired).c_str());
193 return (ThreadSafeMessageBox(strMessage, strCaption, wxYES_NO, parent) == wxYES);
196 void CalledSetStatusBar(const string& strText, int nField)
198 if (pframeMain && pframeMain->m_statusBar)
199 pframeMain->m_statusBar->SetStatusText(strText, nField);
202 void SetDefaultReceivingAddress(const string& strAddress)
204 // Update main window address and database
205 if (pframeMain == NULL)
207 if (strAddress != pframeMain->m_textCtrlAddress->GetValue())
210 if (!AddressToHash160(strAddress, hash160))
212 if (!mapPubKeys.count(hash160))
214 CWalletDB().WriteDefaultKey(mapPubKeys[hash160]);
215 pframeMain->m_textCtrlAddress->SetValue(strAddress);
228 //////////////////////////////////////////////////////////////////////////////
233 CMainFrame::CMainFrame(wxWindow* parent) : CMainFrameBase(parent)
235 Connect(wxEVT_UITHREADCALL, wxCommandEventHandler(CMainFrame::OnUIThreadCall), NULL, this);
237 // Set initially selected page
238 wxNotebookEvent event;
239 event.SetSelection(0);
240 OnNotebookPageChanged(event);
241 m_notebook->ChangeSelection(0);
244 fRefreshListCtrl = false;
245 fRefreshListCtrlRunning = false;
246 fOnSetFocusAddress = false;
248 m_choiceFilter->SetSelection(0);
249 double dResize = 1.0;
251 SetIcon(wxICON(bitcoin));
253 SetIcon(bitcoin80_xpm);
254 SetBackgroundColour(m_toolBar->GetBackgroundColour());
255 wxFont fontTmp = m_staticText41->GetFont();
256 fontTmp.SetFamily(wxFONTFAMILY_TELETYPE);
257 m_staticTextBalance->SetFont(fontTmp);
258 m_staticTextBalance->SetSize(140, 17);
259 // resize to fit ubuntu's huge default font
261 SetSize(dResize * GetSize().GetWidth(), 1.15 * GetSize().GetHeight());
263 m_staticTextBalance->SetLabel(FormatMoney(GetBalance()) + " ");
264 m_listCtrl->SetFocus();
265 ptaskbaricon = new CMyTaskBarIcon();
267 // Mac automatically moves wxID_EXIT, wxID_PREFERENCES and wxID_ABOUT
268 // to their standard places, leaving these menus empty.
269 GetMenuBar()->Remove(2); // remove Help menu
270 GetMenuBar()->Remove(0); // remove File menu
273 // Init column headers
274 int nDateWidth = DateTimeStr(1229413914).size() * 6 + 8;
275 if (!strstr(DateTimeStr(1229413914).c_str(), "2008"))
281 wxListCtrl* pplistCtrl[] = {m_listCtrlAll, m_listCtrlSentReceived, m_listCtrlSent, m_listCtrlReceived};
282 foreach(wxListCtrl* p, pplistCtrl)
284 p->InsertColumn(0, "", wxLIST_FORMAT_LEFT, dResize * 0);
285 p->InsertColumn(1, "", wxLIST_FORMAT_LEFT, dResize * 0);
286 p->InsertColumn(2, _("Status"), wxLIST_FORMAT_LEFT, dResize * 112);
287 p->InsertColumn(3, _("Date"), wxLIST_FORMAT_LEFT, dResize * nDateWidth);
288 p->InsertColumn(4, _("Description"), wxLIST_FORMAT_LEFT, dResize * 409 - nDateWidth);
289 p->InsertColumn(5, _("Debit"), wxLIST_FORMAT_RIGHT, dResize * 79);
290 p->InsertColumn(6, _("Credit"), wxLIST_FORMAT_RIGHT, dResize * 79);
294 int pnWidths[3] = { -100, 88, 300 };
296 pnWidths[1] = pnWidths[1] * 1.1 * dResize;
297 pnWidths[2] = pnWidths[2] * 1.1 * dResize;
299 m_statusBar->SetFieldsCount(3, pnWidths);
301 // Fill your address text box
302 vector<unsigned char> vchPubKey;
303 if (CWalletDB("r").ReadDefaultKey(vchPubKey))
304 m_textCtrlAddress->SetValue(PubKeyToAddress(vchPubKey));
306 // Fill listctrl with wallet transactions
310 CMainFrame::~CMainFrame()
317 void CMainFrame::OnNotebookPageChanged(wxNotebookEvent& event)
320 nPage = event.GetSelection();
323 m_listCtrl = m_listCtrlAll;
324 fShowGenerated = true;
326 fShowReceived = true;
328 else if (nPage == SENTRECEIVED)
330 m_listCtrl = m_listCtrlSentReceived;
331 fShowGenerated = false;
333 fShowReceived = true;
335 else if (nPage == SENT)
337 m_listCtrl = m_listCtrlSent;
338 fShowGenerated = false;
340 fShowReceived = false;
342 else if (nPage == RECEIVED)
344 m_listCtrl = m_listCtrlReceived;
345 fShowGenerated = false;
347 fShowReceived = true;
350 m_listCtrl->SetFocus();
353 void CMainFrame::OnClose(wxCloseEvent& event)
355 if (fMinimizeOnClose && event.CanVeto() && !IsIconized())
357 // Divert close to minimize
359 fClosedToTray = true;
365 CreateThread(Shutdown, NULL);
369 void CMainFrame::OnIconize(wxIconizeEvent& event)
372 // Hide the task bar button when minimized.
373 // Event is sent when the frame is minimized or restored.
374 // wxWidgets 2.8.9 doesn't have IsIconized() so there's no way
375 // to get rid of the deprecated warning. Just ignore it.
376 if (!event.Iconized())
377 fClosedToTray = false;
379 if (mapArgs.count("-minimizetotray")) {
381 // The tray icon sometimes disappears on ubuntu karmic
382 // Hiding the taskbar button doesn't work cleanly on ubuntu lucid
383 // Reports of CPU peg on 64-bit linux
384 if (fMinimizeToTray && event.Iconized())
385 fClosedToTray = true;
386 Show(!fClosedToTray);
387 ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
393 void CMainFrame::OnMouseEvents(wxMouseEvent& event)
397 RAND_add(&event.m_x, sizeof(event.m_x), 0.25);
398 RAND_add(&event.m_y, sizeof(event.m_y), 0.25);
401 void CMainFrame::OnListColBeginDrag(wxListEvent& event)
403 // Hidden columns not resizeable
404 if (event.GetColumn() <= 1 && !fDebug)
410 int CMainFrame::GetSortIndex(const string& strSort)
415 // The wx generic listctrl implementation used on GTK doesn't sort,
416 // so we have to do it ourselves. Remember, we sort in reverse order.
417 // In the wx generic implementation, they store the list of items
418 // in a vector, so indexed lookups are fast, but inserts are slower
419 // the closer they are to the top.
421 int high = m_listCtrl->GetItemCount();
424 int mid = low + ((high - low) / 2);
425 if (strSort.compare(m_listCtrl->GetItemText(mid).c_str()) >= 0)
434 void CMainFrame::InsertLine(bool fNew, int nIndex, uint256 hashKey, string strSort, const wxString& str2, const wxString& str3, const wxString& str4, const wxString& str5, const wxString& str6)
436 strSort = " " + strSort; // leading space to workaround wx2.9.0 ubuntu 9.10 bug
437 long nData = *(long*)&hashKey; // where first char of hidden column is displayed
440 if (!fNew && nIndex == -1)
442 string strHash = " " + hashKey.ToString();
443 while ((nIndex = m_listCtrl->FindItem(nIndex, nData)) != -1)
444 if (GetItemText(m_listCtrl, nIndex, 1) == strHash)
448 // fNew is for blind insert, only use if you're sure it's new
449 if (fNew || nIndex == -1)
451 nIndex = m_listCtrl->InsertItem(GetSortIndex(strSort), strSort);
455 // If sort key changed, must delete and reinsert to make it relocate
456 if (GetItemText(m_listCtrl, nIndex, 0) != strSort)
458 m_listCtrl->DeleteItem(nIndex);
459 nIndex = m_listCtrl->InsertItem(GetSortIndex(strSort), strSort);
463 m_listCtrl->SetItem(nIndex, 1, " " + hashKey.ToString());
464 m_listCtrl->SetItem(nIndex, 2, str2);
465 m_listCtrl->SetItem(nIndex, 3, str3);
466 m_listCtrl->SetItem(nIndex, 4, str4);
467 m_listCtrl->SetItem(nIndex, 5, str5);
468 m_listCtrl->SetItem(nIndex, 6, str6);
469 m_listCtrl->SetItemData(nIndex, nData);
472 bool CMainFrame::DeleteLine(uint256 hashKey)
474 long nData = *(long*)&hashKey;
478 string strHash = " " + hashKey.ToString();
479 while ((nIndex = m_listCtrl->FindItem(nIndex, nData)) != -1)
480 if (GetItemText(m_listCtrl, nIndex, 1) == strHash)
484 m_listCtrl->DeleteItem(nIndex);
489 string FormatTxStatus(const CWalletTx& wtx)
494 if (wtx.nLockTime < 500000000)
495 return strprintf(_("Open for %d blocks"), nBestHeight - wtx.nLockTime);
497 return strprintf(_("Open until %s"), DateTimeStr(wtx.nLockTime).c_str());
501 int nDepth = wtx.GetDepthInMainChain();
502 if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
503 return strprintf(_("%d/offline?"), nDepth);
505 return strprintf(_("%d/unconfirmed"), nDepth);
507 return strprintf(_("%d confirmations"), nDepth);
511 string SingleLine(const string& strIn)
514 bool fOneSpace = false;
515 foreach(int c, strIn)
523 if (fOneSpace && !strOut.empty())
532 bool CMainFrame::InsertTransaction(const CWalletTx& wtx, bool fNew, int nIndex)
534 int64 nTime = wtx.nTimeDisplayed = wtx.GetTxTime();
535 int64 nCredit = wtx.GetCredit(true);
536 int64 nDebit = wtx.GetDebit();
537 int64 nNet = nCredit - nDebit;
538 uint256 hash = wtx.GetHash();
539 string strStatus = FormatTxStatus(wtx);
540 map<string, string> mapValue = wtx.mapValue;
541 wtx.nLinesDisplayed = 1;
545 if (wtx.IsCoinBase())
547 // Don't show generated coin until confirmed by at least one block after it
548 // so we don't get the user's hopes up until it looks like it's probably accepted.
550 // It is not an error when generated blocks are not accepted. By design,
551 // some percentage of blocks, like 10% or more, will end up not accepted.
552 // This is the normal mechanism by which the network copes with latency.
554 // We display regular transactions right away before any confirmation
555 // because they can always get into some block eventually. Generated coins
556 // are special because if their block is not accepted, they are not valid.
558 if (wtx.GetDepthInMainChain() < 2)
560 wtx.nLinesDisplayed = 0;
568 // Find the block the tx is in
569 CBlockIndex* pindex = NULL;
570 map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock);
571 if (mi != mapBlockIndex.end())
572 pindex = (*mi).second;
574 // Sort order, unrecorded transactions sort to the top
575 string strSort = strprintf("%010d-%01d-%010u",
576 (pindex ? pindex->nHeight : INT_MAX),
577 (wtx.IsCoinBase() ? 1 : 0),
581 if (nNet > 0 || wtx.IsCoinBase())
586 string strDescription;
587 if (wtx.IsCoinBase())
590 strDescription = _("Generated");
593 int64 nUnmatured = 0;
594 foreach(const CTxOut& txout, wtx.vout)
595 nUnmatured += txout.GetCredit();
596 if (wtx.IsInMainChain())
598 strDescription = strprintf(_("Generated (%s matures in %d more blocks)"), FormatMoney(nUnmatured).c_str(), wtx.GetBlocksToMaturity());
600 // Check if the block was requested by anyone
601 if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
602 strDescription = _("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
606 strDescription = _("Generated (not accepted)");
610 else if (!mapValue["from"].empty() || !mapValue["message"].empty())
612 // Received by IP connection
615 if (!mapValue["from"].empty())
616 strDescription += _("From: ") + mapValue["from"];
617 if (!mapValue["message"].empty())
619 if (!strDescription.empty())
620 strDescription += " - ";
621 strDescription += mapValue["message"];
626 // Received by Bitcoin Address
629 foreach(const CTxOut& txout, wtx.vout)
633 vector<unsigned char> vchPubKey;
634 if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
636 CRITICAL_BLOCK(cs_mapAddressBook)
638 //strDescription += _("Received payment to ");
639 //strDescription += _("Received with address ");
640 strDescription += _("From: unknown, Received with: ");
641 string strAddress = PubKeyToAddress(vchPubKey);
642 map<string, string>::iterator mi = mapAddressBook.find(strAddress);
643 if (mi != mapAddressBook.end() && !(*mi).second.empty())
645 string strLabel = (*mi).second;
646 strDescription += strAddress.substr(0,12) + "... ";
647 strDescription += "(" + strLabel + ")";
650 strDescription += strAddress;
658 InsertLine(fNew, nIndex, hash, strSort,
660 nTime ? DateTimeStr(nTime) : "",
661 SingleLine(strDescription),
663 FormatMoney(nNet, true));
667 bool fAllFromMe = true;
668 foreach(const CTxIn& txin, wtx.vin)
669 fAllFromMe = fAllFromMe && txin.IsMine();
671 bool fAllToMe = true;
672 foreach(const CTxOut& txout, wtx.vout)
673 fAllToMe = fAllToMe && txout.IsMine();
675 if (fAllFromMe && fAllToMe)
678 int64 nValue = wtx.vout[0].nValue;
679 InsertLine(fNew, nIndex, hash, strSort,
681 nTime ? DateTimeStr(nTime) : "",
682 _("Payment to yourself"),
685 /// issue: can't tell which is the payment and which is the change anymore
686 // FormatMoney(nNet - nValue, true),
687 // FormatMoney(nValue, true));
697 int64 nTxFee = nDebit - wtx.GetValueOut();
698 wtx.nLinesDisplayed = 0;
699 for (int nOut = 0; nOut < wtx.vout.size(); nOut++)
701 const CTxOut& txout = wtx.vout[nOut];
706 if (!mapValue["to"].empty())
709 strAddress = mapValue["to"];
713 // Sent to Bitcoin Address
715 if (ExtractHash160(txout.scriptPubKey, hash160))
716 strAddress = Hash160ToAddress(hash160);
719 string strDescription = _("To: ");
720 CRITICAL_BLOCK(cs_mapAddressBook)
721 if (mapAddressBook.count(strAddress) && !mapAddressBook[strAddress].empty())
722 strDescription += mapAddressBook[strAddress] + " ";
723 strDescription += strAddress;
724 if (!mapValue["message"].empty())
726 if (!strDescription.empty())
727 strDescription += " - ";
728 strDescription += mapValue["message"];
731 int64 nValue = txout.nValue;
738 InsertLine(fNew, nIndex, hash, strprintf("%s-%d", strSort.c_str(), nOut),
740 nTime ? DateTimeStr(nTime) : "",
741 SingleLine(strDescription),
742 FormatMoney(-nValue, true),
744 wtx.nLinesDisplayed++;
750 // Mixed debit transaction, can't break down payees
752 bool fAllMine = true;
753 foreach(const CTxOut& txout, wtx.vout)
754 fAllMine = fAllMine && txout.IsMine();
755 foreach(const CTxIn& txin, wtx.vin)
756 fAllMine = fAllMine && txin.IsMine();
758 InsertLine(fNew, nIndex, hash, strSort,
760 nTime ? DateTimeStr(nTime) : "",
762 FormatMoney(nNet, true),
770 void CMainFrame::RefreshListCtrl()
772 fRefreshListCtrl = true;
776 void CMainFrame::OnIdle(wxIdleEvent& event)
778 if (fRefreshListCtrl)
780 // Collect list of wallet transactions and sort newest first
781 bool fEntered = false;
782 vector<pair<unsigned int, uint256> > vSorted;
783 TRY_CRITICAL_BLOCK(cs_mapWallet)
785 printf("RefreshListCtrl starting\n");
787 fRefreshListCtrl = false;
788 vWalletUpdated.clear();
790 // Do the newest transactions first
791 vSorted.reserve(mapWallet.size());
792 for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
794 const CWalletTx& wtx = (*it).second;
795 unsigned int nTime = UINT_MAX - wtx.GetTxTime();
796 vSorted.push_back(make_pair(nTime, (*it).first));
798 m_listCtrl->DeleteAllItems();
803 sort(vSorted.begin(), vSorted.end());
806 for (int i = 0; i < vSorted.size();)
810 bool fEntered = false;
811 TRY_CRITICAL_BLOCK(cs_mapWallet)
814 uint256& hash = vSorted[i++].second;
815 map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
816 if (mi != mapWallet.end())
817 InsertTransaction((*mi).second, true);
819 if (!fEntered || i == 100 || i % 500 == 0)
823 printf("RefreshListCtrl done\n");
825 // Update transaction total display
830 // Check for time updates
831 static int64 nLastTime;
832 if (GetTime() > nLastTime + 30)
834 TRY_CRITICAL_BLOCK(cs_mapWallet)
836 nLastTime = GetTime();
837 for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
839 CWalletTx& wtx = (*it).second;
840 if (wtx.nTimeDisplayed && wtx.nTimeDisplayed != wtx.GetTxTime())
841 InsertTransaction(wtx, false);
848 void CMainFrame::RefreshStatusColumn()
851 static CBlockIndex* pindexLastBest;
852 static unsigned int nLastRefreshed;
854 int nTop = max((int)m_listCtrl->GetTopItem(), 0);
855 if (nTop == nLastTop && pindexLastBest == pindexBest)
858 TRY_CRITICAL_BLOCK(cs_mapWallet)
861 int nEnd = min(nStart + 100, m_listCtrl->GetItemCount());
863 if (pindexLastBest == pindexBest && nLastRefreshed == nListViewUpdated)
865 // If no updates, only need to do the part that moved onto the screen
866 if (nStart >= nLastTop && nStart < nLastTop + 100)
867 nStart = nLastTop + 100;
868 if (nEnd >= nLastTop && nEnd < nLastTop + 100)
872 pindexLastBest = pindexBest;
873 nLastRefreshed = nListViewUpdated;
875 for (int nIndex = nStart; nIndex < min(nEnd, m_listCtrl->GetItemCount()); nIndex++)
877 uint256 hash((string)GetItemText(m_listCtrl, nIndex, 1));
878 map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
879 if (mi == mapWallet.end())
881 printf("CMainFrame::RefreshStatusColumn() : tx not found in mapWallet\n");
884 CWalletTx& wtx = (*mi).second;
885 if (wtx.IsCoinBase() || wtx.GetTxTime() != wtx.nTimeDisplayed)
887 if (!InsertTransaction(wtx, false, nIndex))
888 m_listCtrl->DeleteItem(nIndex--);
891 m_listCtrl->SetItem(nIndex, 2, FormatTxStatus(wtx));
896 void CMainFrame::OnPaint(wxPaintEvent& event)
907 unsigned int nNeedRepaint = 0;
908 unsigned int nLastRepaint = 0;
909 int64 nLastRepaintTime = 0;
910 int64 nRepaintInterval = 500;
912 void ThreadDelayedRepaint(void* parg)
916 if (nLastRepaint != nNeedRepaint && GetTimeMillis() - nLastRepaintTime >= nRepaintInterval)
918 nLastRepaint = nNeedRepaint;
921 printf("DelayedRepaint\n");
923 pframeMain->fRefresh = true;
924 pframeMain->GetEventHandler()->AddPendingEvent(event);
927 Sleep(nRepaintInterval);
931 void MainFrameRepaint()
933 // This is called by network code that shouldn't access pframeMain
934 // directly because it could still be running after the UI is closed.
937 // Don't repaint too often
938 static int64 nLastRepaintRequest;
939 if (GetTimeMillis() - nLastRepaintRequest < 100)
944 nLastRepaintRequest = GetTimeMillis();
946 printf("MainFrameRepaint\n");
948 pframeMain->fRefresh = true;
949 pframeMain->GetEventHandler()->AddPendingEvent(event);
953 void CMainFrame::OnPaintListCtrl(wxPaintEvent& event)
955 // Skip lets the listctrl do the paint, we're just hooking the message
959 ptaskbaricon->UpdateTooltip();
964 static int nTransactionCount;
965 bool fPaintedBalance = false;
966 if (GetTimeMillis() - nLastRepaintTime >= nRepaintInterval)
968 nLastRepaint = nNeedRepaint;
969 nLastRepaintTime = GetTimeMillis();
971 // Update listctrl contents
972 if (!vWalletUpdated.empty())
974 TRY_CRITICAL_BLOCK(cs_mapWallet)
977 if (m_listCtrl->GetItemCount())
978 strTop = (string)m_listCtrl->GetItemText(0);
979 foreach(uint256 hash, vWalletUpdated)
981 map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
982 if (mi != mapWallet.end())
983 InsertTransaction((*mi).second, false);
985 vWalletUpdated.clear();
986 if (m_listCtrl->GetItemCount() && strTop != (string)m_listCtrl->GetItemText(0))
987 m_listCtrl->ScrollList(0, INT_MIN/2);
992 TRY_CRITICAL_BLOCK(cs_mapWallet)
994 fPaintedBalance = true;
995 m_staticTextBalance->SetLabel(FormatMoney(GetBalance()) + " ");
997 // Count hidden and multi-line transactions
998 nTransactionCount = 0;
999 for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
1001 CWalletTx& wtx = (*it).second;
1002 nTransactionCount += wtx.nLinesDisplayed;
1006 if (!vWalletUpdated.empty() || !fPaintedBalance)
1009 // Update status column of visible items only
1010 RefreshStatusColumn();
1012 // Update status bar
1014 if (fGenerateBitcoins)
1015 strGen = _(" Generating");
1016 if (fGenerateBitcoins && vNodes.empty())
1017 strGen = _("(not connected)");
1018 m_statusBar->SetStatusText(strGen, 1);
1020 string strStatus = strprintf(_(" %d connections %d blocks %d transactions"), vNodes.size(), nBestHeight + 1, nTransactionCount);
1021 m_statusBar->SetStatusText(strStatus, 2);
1023 if (fDebug && GetTime() - nThreadSocketHandlerHeartbeat > 60)
1024 m_statusBar->SetStatusText(" ERROR: ThreadSocketHandler has stopped", 0);
1026 // Update receiving address
1027 string strDefaultAddress = PubKeyToAddress(vchDefaultKey);
1028 if (m_textCtrlAddress->GetValue() != strDefaultAddress)
1029 m_textCtrlAddress->SetValue(strDefaultAddress);
1033 void UIThreadCall(boost::function0<void> fn)
1035 // Call this with a function object created with bind.
1036 // bind needs all parameters to match the function's expected types
1037 // and all default parameters specified. Some examples:
1038 // UIThreadCall(bind(wxBell));
1039 // UIThreadCall(bind(wxMessageBox, wxT("Message"), wxT("Title"), wxOK, (wxWindow*)NULL, -1, -1));
1040 // UIThreadCall(bind(&CMainFrame::OnMenuHelpAbout, pframeMain, event));
1043 wxCommandEvent event(wxEVT_UITHREADCALL);
1044 event.SetClientData((void*)new boost::function0<void>(fn));
1045 pframeMain->GetEventHandler()->AddPendingEvent(event);
1049 void CMainFrame::OnUIThreadCall(wxCommandEvent& event)
1051 boost::function0<void>* pfn = (boost::function0<void>*)event.GetClientData();
1056 void CMainFrame::OnMenuFileExit(wxCommandEvent& event)
1062 void CMainFrame::OnMenuOptionsGenerate(wxCommandEvent& event)
1064 // Options->Generate Coins
1065 GenerateBitcoins(event.IsChecked());
1068 void CMainFrame::OnUpdateUIOptionsGenerate(wxUpdateUIEvent& event)
1070 event.Check(fGenerateBitcoins);
1073 void CMainFrame::OnMenuOptionsChangeYourAddress(wxCommandEvent& event)
1075 // Options->Your Receiving Addresses
1076 CAddressBookDialog dialog(this, "", CAddressBookDialog::RECEIVING, false);
1077 if (!dialog.ShowModal())
1081 void CMainFrame::OnMenuOptionsOptions(wxCommandEvent& event)
1084 COptionsDialog dialog(this);
1088 void CMainFrame::OnMenuHelpAbout(wxCommandEvent& event)
1091 CAboutDialog dialog(this);
1095 void CMainFrame::OnButtonSend(wxCommandEvent& event)
1098 CSendDialog dialog(this);
1102 void CMainFrame::OnButtonAddressBook(wxCommandEvent& event)
1104 // Toolbar: Address Book
1105 CAddressBookDialog dialogAddr(this, "", CAddressBookDialog::SENDING, false);
1106 if (dialogAddr.ShowModal() == 2)
1109 CSendDialog dialogSend(this, dialogAddr.GetSelectedAddress());
1110 dialogSend.ShowModal();
1114 void CMainFrame::OnSetFocusAddress(wxFocusEvent& event)
1116 // Automatically select-all when entering window
1118 m_textCtrlAddress->SetSelection(-1, -1);
1119 fOnSetFocusAddress = true;
1122 void CMainFrame::OnMouseEventsAddress(wxMouseEvent& event)
1125 if (fOnSetFocusAddress)
1126 m_textCtrlAddress->SetSelection(-1, -1);
1127 fOnSetFocusAddress = false;
1130 void CMainFrame::OnButtonNew(wxCommandEvent& event)
1133 CGetTextFromUserDialog dialog(this,
1134 _("New Receiving Address"),
1135 _("You should use a new address for each payment you receive.\n\nLabel"),
1137 if (!dialog.ShowModal())
1139 string strName = dialog.GetValue();
1142 string strAddress = PubKeyToAddress(GenerateNewKey());
1145 SetAddressBookName(strAddress, strName);
1146 SetDefaultReceivingAddress(strAddress);
1149 void CMainFrame::OnButtonCopy(wxCommandEvent& event)
1151 // Copy address box to clipboard
1152 if (wxTheClipboard->Open())
1154 wxTheClipboard->SetData(new wxTextDataObject(m_textCtrlAddress->GetValue()));
1155 wxTheClipboard->Close();
1159 void CMainFrame::OnListItemActivated(wxListEvent& event)
1161 uint256 hash((string)GetItemText(m_listCtrl, event.GetIndex(), 1));
1163 CRITICAL_BLOCK(cs_mapWallet)
1165 map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
1166 if (mi == mapWallet.end())
1168 printf("CMainFrame::OnListItemActivated() : tx not found in mapWallet\n");
1173 CTxDetailsDialog dialog(this, wtx);
1175 //CTxDetailsDialog* pdialog = new CTxDetailsDialog(this, wtx);
1184 //////////////////////////////////////////////////////////////////////////////
1189 CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetailsDialogBase(parent)
1191 CRITICAL_BLOCK(cs_mapAddressBook)
1194 strHTML.reserve(4000);
1195 strHTML += "<html><font face='verdana, arial, helvetica, sans-serif'>";
1197 int64 nTime = wtx.GetTxTime();
1198 int64 nCredit = wtx.GetCredit();
1199 int64 nDebit = wtx.GetDebit();
1200 int64 nNet = nCredit - nDebit;
1204 strHTML += _("<b>Status:</b> ") + FormatTxStatus(wtx);
1205 int nRequests = wtx.GetRequestCount();
1206 if (nRequests != -1)
1209 strHTML += _(", has not been successfully broadcast yet");
1210 else if (nRequests == 1)
1211 strHTML += strprintf(_(", broadcast through %d node"), nRequests);
1213 strHTML += strprintf(_(", broadcast through %d nodes"), nRequests);
1217 strHTML += _("<b>Date:</b> ") + (nTime ? DateTimeStr(nTime) : "") + "<br>";
1223 if (wtx.IsCoinBase())
1225 strHTML += _("<b>Source:</b> Generated<br>");
1227 else if (!wtx.mapValue["from"].empty())
1229 // Online transaction
1230 if (!wtx.mapValue["from"].empty())
1231 strHTML += _("<b>From:</b> ") + HtmlEscape(wtx.mapValue["from"]) + "<br>";
1235 // Offline transaction
1239 foreach(const CTxOut& txout, wtx.vout)
1243 vector<unsigned char> vchPubKey;
1244 if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
1246 string strAddress = PubKeyToAddress(vchPubKey);
1247 if (mapAddressBook.count(strAddress))
1249 strHTML += string() + _("<b>From:</b> ") + _("unknown") + "<br>";
1250 strHTML += _("<b>To:</b> ");
1251 strHTML += HtmlEscape(strAddress);
1252 if (!mapAddressBook[strAddress].empty())
1253 strHTML += _(" (yours, label: ") + mapAddressBook[strAddress] + ")";
1255 strHTML += _(" (yours)");
1270 if (!wtx.mapValue["to"].empty())
1272 // Online transaction
1273 strAddress = wtx.mapValue["to"];
1274 strHTML += _("<b>To:</b> ");
1275 if (mapAddressBook.count(strAddress) && !mapAddressBook[strAddress].empty())
1276 strHTML += mapAddressBook[strAddress] + " ";
1277 strHTML += HtmlEscape(strAddress) + "<br>";
1284 if (wtx.IsCoinBase() && nCredit == 0)
1289 int64 nUnmatured = 0;
1290 foreach(const CTxOut& txout, wtx.vout)
1291 nUnmatured += txout.GetCredit();
1292 strHTML += _("<b>Credit:</b> ");
1293 if (wtx.IsInMainChain())
1294 strHTML += strprintf(_("(%s matures in %d more blocks)"), FormatMoney(nUnmatured).c_str(), wtx.GetBlocksToMaturity());
1296 strHTML += _("(not accepted)");
1304 strHTML += _("<b>Credit:</b> ") + FormatMoney(nNet) + "<br>";
1308 bool fAllFromMe = true;
1309 foreach(const CTxIn& txin, wtx.vin)
1310 fAllFromMe = fAllFromMe && txin.IsMine();
1312 bool fAllToMe = true;
1313 foreach(const CTxOut& txout, wtx.vout)
1314 fAllToMe = fAllToMe && txout.IsMine();
1321 foreach(const CTxOut& txout, wtx.vout)
1326 if (wtx.mapValue["to"].empty())
1328 // Offline transaction
1330 if (ExtractHash160(txout.scriptPubKey, hash160))
1332 string strAddress = Hash160ToAddress(hash160);
1333 strHTML += _("<b>To:</b> ");
1334 if (mapAddressBook.count(strAddress) && !mapAddressBook[strAddress].empty())
1335 strHTML += mapAddressBook[strAddress] + " ";
1336 strHTML += strAddress;
1341 strHTML += _("<b>Debit:</b> ") + FormatMoney(-txout.nValue) + "<br>";
1347 /// issue: can't tell which is the payment and which is the change anymore
1348 //int64 nValue = wtx.vout[0].nValue;
1349 //strHTML += _("<b>Debit:</b> ") + FormatMoney(-nValue) + "<br>";
1350 //strHTML += _("<b>Credit:</b> ") + FormatMoney(nValue) + "<br>";
1353 int64 nTxFee = nDebit - wtx.GetValueOut();
1355 strHTML += _("<b>Transaction fee:</b> ") + FormatMoney(-nTxFee) + "<br>";
1360 // Mixed debit transaction
1362 foreach(const CTxIn& txin, wtx.vin)
1364 strHTML += _("<b>Debit:</b> ") + FormatMoney(-txin.GetDebit()) + "<br>";
1365 foreach(const CTxOut& txout, wtx.vout)
1367 strHTML += _("<b>Credit:</b> ") + FormatMoney(txout.GetCredit()) + "<br>";
1371 strHTML += _("<b>Net amount:</b> ") + FormatMoney(nNet, true) + "<br>";
1377 if (!wtx.mapValue["message"].empty())
1378 strHTML += string() + "<br><b>" + _("Message:") + "</b><br>" + HtmlEscape(wtx.mapValue["message"], true) + "<br>";
1380 if (wtx.IsCoinBase())
1381 strHTML += string() + "<br>" + _("Generated coins must wait 120 blocks before they can be spent. When you generated this block, it was broadcast to the network to be added to the block chain. If it fails to get into the chain, it will change to \"not accepted\" and not be spendable. This may occasionally happen if another node generates a block within a few seconds of yours.") + "<br>";
1389 strHTML += "<hr><br>debug print<br><br>";
1390 foreach(const CTxIn& txin, wtx.vin)
1392 strHTML += "<b>Debit:</b> " + FormatMoney(-txin.GetDebit()) + "<br>";
1393 foreach(const CTxOut& txout, wtx.vout)
1395 strHTML += "<b>Credit:</b> " + FormatMoney(txout.GetCredit()) + "<br>";
1397 strHTML += "<b>Inputs:</b><br>";
1398 CRITICAL_BLOCK(cs_mapWallet)
1400 foreach(const CTxIn& txin, wtx.vin)
1402 COutPoint prevout = txin.prevout;
1403 map<uint256, CWalletTx>::iterator mi = mapWallet.find(prevout.hash);
1404 if (mi != mapWallet.end())
1406 const CWalletTx& prev = (*mi).second;
1407 if (prevout.n < prev.vout.size())
1409 strHTML += HtmlEscape(prev.ToString(), true);
1410 strHTML += " " + FormatTxStatus(prev) + ", ";
1411 strHTML = strHTML + "IsMine=" + (prev.vout[prevout.n].IsMine() ? "true" : "false") + "<br>";
1417 strHTML += "<br><hr><br><b>Transaction:</b><br>";
1418 strHTML += HtmlEscape(wtx.ToString(), true);
1423 strHTML += "</font></html>";
1424 string(strHTML.begin(), strHTML.end()).swap(strHTML);
1425 m_htmlWin->SetPage(strHTML);
1426 m_buttonOK->SetFocus();
1430 void CTxDetailsDialog::OnButtonOK(wxCommandEvent& event)
1440 //////////////////////////////////////////////////////////////////////////////
1445 COptionsDialog::COptionsDialog(wxWindow* parent) : COptionsDialogBase(parent)
1447 // Set up list box of page choices
1448 m_listBox->Append(_("Main"));
1449 //m_listBox->Append(_("Test 2"));
1450 m_listBox->SetSelection(0);
1453 m_checkBoxStartOnSystemStartup->SetLabel(_("&Start Bitcoin on window system startup"));
1454 if (!mapArgs.count("-minimizetotray"))
1456 // Minimize to tray is just too buggy on Linux
1457 fMinimizeToTray = false;
1458 m_checkBoxMinimizeToTray->SetValue(false);
1459 m_checkBoxMinimizeToTray->Enable(false);
1460 m_checkBoxMinimizeOnClose->SetLabel(_("&Minimize on close"));
1463 #ifdef __WXMAC_OSX__
1464 m_checkBoxStartOnSystemStartup->Enable(false); // not implemented yet
1468 m_textCtrlTransactionFee->SetValue(FormatMoney(nTransactionFee));
1469 m_checkBoxLimitProcessors->SetValue(fLimitProcessors);
1470 m_spinCtrlLimitProcessors->Enable(fLimitProcessors);
1471 m_spinCtrlLimitProcessors->SetValue(nLimitProcessors);
1472 int nProcessors = wxThread::GetCPUCount();
1473 if (nProcessors < 1)
1475 m_spinCtrlLimitProcessors->SetRange(1, nProcessors);
1476 m_checkBoxStartOnSystemStartup->SetValue(fTmpStartOnSystemStartup = GetStartOnSystemStartup());
1477 m_checkBoxMinimizeToTray->SetValue(fMinimizeToTray);
1478 m_checkBoxMinimizeOnClose->SetValue(fMinimizeOnClose);
1479 m_checkBoxUseProxy->SetValue(fUseProxy);
1480 m_textCtrlProxyIP->Enable(fUseProxy);
1481 m_textCtrlProxyPort->Enable(fUseProxy);
1482 m_staticTextProxyIP->Enable(fUseProxy);
1483 m_staticTextProxyPort->Enable(fUseProxy);
1484 m_textCtrlProxyIP->SetValue(addrProxy.ToStringIP());
1485 m_textCtrlProxyPort->SetValue(addrProxy.ToStringPort());
1487 m_buttonOK->SetFocus();
1490 void COptionsDialog::SelectPage(int nPage)
1492 m_panelMain->Show(nPage == 0);
1493 m_panelTest2->Show(nPage == 1);
1495 m_scrolledWindow->Layout();
1496 m_scrolledWindow->SetScrollbars(0, 0, 0, 0, 0, 0);
1499 void COptionsDialog::OnListBox(wxCommandEvent& event)
1501 SelectPage(event.GetSelection());
1504 void COptionsDialog::OnKillFocusTransactionFee(wxFocusEvent& event)
1507 int64 nTmp = nTransactionFee;
1508 ParseMoney(m_textCtrlTransactionFee->GetValue(), nTmp);
1509 m_textCtrlTransactionFee->SetValue(FormatMoney(nTmp));
1512 void COptionsDialog::OnCheckBoxLimitProcessors(wxCommandEvent& event)
1514 m_spinCtrlLimitProcessors->Enable(event.IsChecked());
1517 void COptionsDialog::OnCheckBoxUseProxy(wxCommandEvent& event)
1519 m_textCtrlProxyIP->Enable(event.IsChecked());
1520 m_textCtrlProxyPort->Enable(event.IsChecked());
1521 m_staticTextProxyIP->Enable(event.IsChecked());
1522 m_staticTextProxyPort->Enable(event.IsChecked());
1525 CAddress COptionsDialog::GetProxyAddr()
1527 // Be careful about byte order, addr.ip and addr.port are big endian
1528 CAddress addr(m_textCtrlProxyIP->GetValue() + ":" + m_textCtrlProxyPort->GetValue());
1529 if (addr.ip == INADDR_NONE)
1530 addr.ip = addrProxy.ip;
1531 int nPort = atoi(m_textCtrlProxyPort->GetValue());
1532 addr.port = htons(nPort);
1533 if (nPort <= 0 || nPort > USHRT_MAX)
1534 addr.port = addrProxy.port;
1538 void COptionsDialog::OnKillFocusProxy(wxFocusEvent& event)
1541 m_textCtrlProxyIP->SetValue(GetProxyAddr().ToStringIP());
1542 m_textCtrlProxyPort->SetValue(GetProxyAddr().ToStringPort());
1546 void COptionsDialog::OnButtonOK(wxCommandEvent& event)
1548 OnButtonApply(event);
1552 void COptionsDialog::OnButtonCancel(wxCommandEvent& event)
1557 void COptionsDialog::OnButtonApply(wxCommandEvent& event)
1561 int64 nPrevTransactionFee = nTransactionFee;
1562 if (ParseMoney(m_textCtrlTransactionFee->GetValue(), nTransactionFee) && nTransactionFee != nPrevTransactionFee)
1563 walletdb.WriteSetting("nTransactionFee", nTransactionFee);
1565 int nPrevMaxProc = (fLimitProcessors ? nLimitProcessors : INT_MAX);
1566 if (fLimitProcessors != m_checkBoxLimitProcessors->GetValue())
1568 fLimitProcessors = m_checkBoxLimitProcessors->GetValue();
1569 walletdb.WriteSetting("fLimitProcessors", fLimitProcessors);
1571 if (nLimitProcessors != m_spinCtrlLimitProcessors->GetValue())
1573 nLimitProcessors = m_spinCtrlLimitProcessors->GetValue();
1574 walletdb.WriteSetting("nLimitProcessors", nLimitProcessors);
1576 if (fGenerateBitcoins && (fLimitProcessors ? nLimitProcessors : INT_MAX) > nPrevMaxProc)
1577 GenerateBitcoins(fGenerateBitcoins);
1579 if (fTmpStartOnSystemStartup != m_checkBoxStartOnSystemStartup->GetValue())
1581 fTmpStartOnSystemStartup = m_checkBoxStartOnSystemStartup->GetValue();
1582 SetStartOnSystemStartup(fTmpStartOnSystemStartup);
1585 if (fMinimizeToTray != m_checkBoxMinimizeToTray->GetValue())
1587 fMinimizeToTray = m_checkBoxMinimizeToTray->GetValue();
1588 walletdb.WriteSetting("fMinimizeToTray", fMinimizeToTray);
1589 ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
1592 if (fMinimizeOnClose != m_checkBoxMinimizeOnClose->GetValue())
1594 fMinimizeOnClose = m_checkBoxMinimizeOnClose->GetValue();
1595 walletdb.WriteSetting("fMinimizeOnClose", fMinimizeOnClose);
1598 fUseProxy = m_checkBoxUseProxy->GetValue();
1599 walletdb.WriteSetting("fUseProxy", fUseProxy);
1601 addrProxy = GetProxyAddr();
1602 walletdb.WriteSetting("addrProxy", addrProxy);
1609 //////////////////////////////////////////////////////////////////////////////
1614 CAboutDialog::CAboutDialog(wxWindow* parent) : CAboutDialogBase(parent)
1616 m_staticTextVersion->SetLabel(strprintf(_("version %d.%d.%d beta"), VERSION/10000, (VERSION/100)%100, VERSION%100));
1618 // Change (c) into UTF-8 or ANSI copyright symbol
1619 wxString str = m_staticTextMain->GetLabel();
1621 str.Replace("(c)", wxString::FromUTF8("\xC2\xA9"));
1623 str.Replace("(c)", "\xA9");
1625 m_staticTextMain->SetLabel(str);
1627 // Resize on Linux to make the window fit the text.
1628 // The text was wrapped manually rather than using the Wrap setting because
1629 // the wrap would be too small on Linux and it can't be changed at this point.
1630 wxFont fontTmp = m_staticTextMain->GetFont();
1631 if (fontTmp.GetPointSize() > 8);
1632 fontTmp.SetPointSize(8);
1633 m_staticTextMain->SetFont(fontTmp);
1634 SetSize(GetSize().GetWidth() + 44, GetSize().GetHeight() + 10);
1638 void CAboutDialog::OnButtonOK(wxCommandEvent& event)
1648 //////////////////////////////////////////////////////////////////////////////
1653 CSendDialog::CSendDialog(wxWindow* parent, const wxString& strAddress) : CSendDialogBase(parent)
1656 m_textCtrlAddress->SetValue(strAddress);
1657 m_choiceTransferType->SetSelection(0);
1658 m_bitmapCheckMark->Show(false);
1659 fEnabledPrev = true;
1660 m_textCtrlAddress->SetFocus();
1661 //// todo: should add a display of your balance for convenience
1663 wxFont fontTmp = m_staticTextInstructions->GetFont();
1664 if (fontTmp.GetPointSize() > 9);
1665 fontTmp.SetPointSize(9);
1666 m_staticTextInstructions->SetFont(fontTmp);
1672 iconSend.CopyFromBitmap(wxBitmap(send16noshadow_xpm));
1675 wxCommandEvent event;
1676 OnTextAddress(event);
1678 // Fixup the tab order
1679 m_buttonPaste->MoveAfterInTabOrder(m_buttonCancel);
1680 m_buttonAddress->MoveAfterInTabOrder(m_buttonPaste);
1684 void CSendDialog::OnTextAddress(wxCommandEvent& event)
1688 bool fBitcoinAddress = IsValidBitcoinAddress(m_textCtrlAddress->GetValue());
1689 m_bitmapCheckMark->Show(fBitcoinAddress);
1691 // Grey out message if bitcoin address
1692 bool fEnable = !fBitcoinAddress;
1693 m_staticTextFrom->Enable(fEnable);
1694 m_textCtrlFrom->Enable(fEnable);
1695 m_staticTextMessage->Enable(fEnable);
1696 m_textCtrlMessage->Enable(fEnable);
1697 m_textCtrlMessage->SetBackgroundColour(wxSystemSettings::GetColour(fEnable ? wxSYS_COLOUR_WINDOW : wxSYS_COLOUR_BTNFACE));
1698 if (!fEnable && fEnabledPrev)
1700 strFromSave = m_textCtrlFrom->GetValue();
1701 strMessageSave = m_textCtrlMessage->GetValue();
1702 m_textCtrlFrom->SetValue(_("Will appear as \"From: Unknown\""));
1703 m_textCtrlMessage->SetValue(_("Can't include a message when sending to a Bitcoin address"));
1705 else if (fEnable && !fEnabledPrev)
1707 m_textCtrlFrom->SetValue(strFromSave);
1708 m_textCtrlMessage->SetValue(strMessageSave);
1710 fEnabledPrev = fEnable;
1713 void CSendDialog::OnKillFocusAmount(wxFocusEvent& event)
1715 // Reformat the amount
1717 if (m_textCtrlAmount->GetValue().Trim().empty())
1720 if (ParseMoney(m_textCtrlAmount->GetValue(), nTmp))
1721 m_textCtrlAmount->SetValue(FormatMoney(nTmp));
1724 void CSendDialog::OnButtonAddressBook(wxCommandEvent& event)
1726 // Open address book
1727 CAddressBookDialog dialog(this, m_textCtrlAddress->GetValue(), CAddressBookDialog::SENDING, true);
1728 if (dialog.ShowModal())
1729 m_textCtrlAddress->SetValue(dialog.GetSelectedAddress());
1732 void CSendDialog::OnButtonPaste(wxCommandEvent& event)
1734 // Copy clipboard to address box
1735 if (wxTheClipboard->Open())
1737 if (wxTheClipboard->IsSupported(wxDF_TEXT))
1739 wxTextDataObject data;
1740 wxTheClipboard->GetData(data);
1741 m_textCtrlAddress->SetValue(data.GetText());
1743 wxTheClipboard->Close();
1747 void CSendDialog::OnButtonSend(wxCommandEvent& event)
1750 string strAddress = (string)m_textCtrlAddress->GetValue();
1754 if (!ParseMoney(m_textCtrlAmount->GetValue(), nValue) || nValue <= 0)
1756 wxMessageBox(_("Error in amount "), _("Send Coins"));
1759 if (nValue > GetBalance())
1761 wxMessageBox(_("Amount exceeds your balance "), _("Send Coins"));
1764 if (nValue + nTransactionFee > GetBalance())
1766 wxMessageBox(string(_("Total exceeds your balance when the ")) + FormatMoney(nTransactionFee) + _(" transaction fee is included "), _("Send Coins"));
1770 // Parse bitcoin address
1772 bool fBitcoinAddress = AddressToHash160(strAddress, hash160);
1774 if (fBitcoinAddress)
1776 // Send to bitcoin address
1777 CScript scriptPubKey;
1778 scriptPubKey << OP_DUP << OP_HASH160 << hash160 << OP_EQUALVERIFY << OP_CHECKSIG;
1780 string strError = SendMoney(scriptPubKey, nValue, wtx, true);
1782 wxMessageBox(_("Payment sent "), _("Sending..."));
1783 else if (strError != "ABORTED")
1784 wxMessageBox(strError + " ", _("Sending..."));
1789 CAddress addr(strAddress);
1790 if (!addr.IsValid())
1792 wxMessageBox(_("Invalid address "), _("Send Coins"));
1797 wtx.mapValue["to"] = strAddress;
1798 wtx.mapValue["from"] = m_textCtrlFrom->GetValue();
1799 wtx.mapValue["message"] = m_textCtrlMessage->GetValue();
1801 // Send to IP address
1802 CSendingDialog* pdialog = new CSendingDialog(this, addr, nValue, wtx);
1803 if (!pdialog->ShowModal())
1807 CRITICAL_BLOCK(cs_mapAddressBook)
1808 if (!mapAddressBook.count(strAddress))
1809 SetAddressBookName(strAddress, "");
1814 void CSendDialog::OnButtonCancel(wxCommandEvent& event)
1825 //////////////////////////////////////////////////////////////////////////////
1830 CSendingDialog::CSendingDialog(wxWindow* parent, const CAddress& addrIn, int64 nPriceIn, const CWalletTx& wtxIn) : CSendingDialogBase(NULL) // we have to give null so parent can't destroy us
1835 start = wxDateTime::UNow();
1836 memset(pszStatus, 0, sizeof(pszStatus));
1843 SetSize(1.2 * GetSize().GetWidth(), 1.08 * GetSize().GetHeight());
1846 SetTitle(strprintf(_("Sending %s to %s"), FormatMoney(nPrice).c_str(), wtx.mapValue["to"].c_str()));
1847 m_textCtrlStatus->SetValue("");
1849 CreateThread(SendingDialogStartTransfer, this);
1852 CSendingDialog::~CSendingDialog()
1854 printf("~CSendingDialog()\n");
1857 void CSendingDialog::Close()
1859 // Last one out turn out the lights.
1860 // fWorkDone signals that work side is done and UI thread should call destroy.
1861 // fUIDone signals that UI window has closed and work thread should call destroy.
1862 // This allows the window to disappear and end modality when cancelled
1863 // without making the user wait for ConnectNode to return. The dialog object
1864 // hangs around in the background until the work thread exits.
1875 void CSendingDialog::OnClose(wxCloseEvent& event)
1877 if (!event.CanVeto() || fWorkDone || fAbort || !fCanCancel)
1884 wxCommandEvent cmdevent;
1885 OnButtonCancel(cmdevent);
1889 void CSendingDialog::OnButtonOK(wxCommandEvent& event)
1895 void CSendingDialog::OnButtonCancel(wxCommandEvent& event)
1901 void CSendingDialog::OnPaint(wxPaintEvent& event)
1904 if (strlen(pszStatus) > 130)
1905 m_textCtrlStatus->SetValue(string("\n") + pszStatus);
1907 m_textCtrlStatus->SetValue(string("\n\n") + pszStatus);
1908 m_staticTextSending->SetFocus();
1910 m_buttonCancel->Enable(false);
1913 m_buttonOK->Enable(true);
1914 m_buttonOK->SetFocus();
1915 m_buttonCancel->Enable(false);
1917 if (fAbort && fCanCancel && IsShown())
1919 strcpy(pszStatus, _("CANCELLED"));
1920 m_buttonOK->Enable(true);
1921 m_buttonOK->SetFocus();
1922 m_buttonCancel->Enable(false);
1923 m_buttonCancel->SetLabel(_("Cancelled"));
1925 wxMessageBox(_("Transfer cancelled "), _("Sending..."), wxOK, this);
1931 // Everything from here on is not in the UI thread and must only communicate
1932 // with the rest of the dialog through variables and calling repaint.
1935 void CSendingDialog::Repaint()
1939 GetEventHandler()->AddPendingEvent(event);
1942 bool CSendingDialog::Status()
1949 if (fAbort && fCanCancel)
1951 memset(pszStatus, 0, 10);
1952 strcpy(pszStatus, _("CANCELLED"));
1960 bool CSendingDialog::Status(const string& str)
1965 // This can be read by the UI thread at any time,
1966 // so copy in a way that can be read cleanly at all times.
1967 memset(pszStatus, 0, min(str.size()+1, sizeof(pszStatus)));
1968 strlcpy(pszStatus, str.c_str(), sizeof(pszStatus));
1974 bool CSendingDialog::Error(const string& str)
1978 Status(string(_("Error: ")) + str);
1982 void SendingDialogStartTransfer(void* parg)
1984 ((CSendingDialog*)parg)->StartTransfer();
1987 void CSendingDialog::StartTransfer()
1989 // Make sure we have enough money
1990 if (nPrice + nTransactionFee > GetBalance())
1992 Error(_("Insufficient funds"));
1996 // We may have connected already for product details
1997 if (!Status(_("Connecting...")))
1999 CNode* pnode = ConnectNode(addr, 15 * 60);
2002 Error(_("Unable to connect"));
2006 // Send order to seller, with response going to OnReply2 via event handler
2007 if (!Status(_("Requesting public key...")))
2009 pnode->PushRequest("checkorder", wtx, SendingDialogOnReply2, this);
2012 void SendingDialogOnReply2(void* parg, CDataStream& vRecv)
2014 ((CSendingDialog*)parg)->OnReply2(vRecv);
2017 void CSendingDialog::OnReply2(CDataStream& vRecv)
2019 if (!Status(_("Received public key...")))
2022 CScript scriptPubKey;
2030 vRecv >> strMessage;
2031 Error(_("Transfer was not accepted"));
2032 //// todo: enlarge the window and enable a hidden white box to put seller's message
2035 vRecv >> scriptPubKey;
2039 //// what do we want to do about this?
2040 Error(_("Invalid response received"));
2044 // Pause to give the user a chance to cancel
2045 while (wxDateTime::UNow() < start + wxTimeSpan(0, 0, 0, 2 * 1000))
2052 CRITICAL_BLOCK(cs_main)
2055 if (!Status(_("Creating transaction...")))
2057 if (nPrice + nTransactionFee > GetBalance())
2059 Error(_("Insufficient funds"));
2064 if (!CreateTransaction(scriptPubKey, nPrice, wtx, key, nFeeRequired))
2066 if (nPrice + nFeeRequired > GetBalance())
2067 Error(strprintf(_("This is an oversized transaction that requires a transaction fee of %s"), FormatMoney(nFeeRequired).c_str()));
2069 Error(_("Transaction creation failed"));
2074 if (!ThreadSafeAskFee(nFeeRequired, _("Sending..."), this))
2076 Error(_("Transaction aborted"));
2080 // Make sure we're still connected
2081 CNode* pnode = ConnectNode(addr, 2 * 60 * 60);
2084 Error(_("Lost connection, transaction cancelled"));
2088 // Last chance to cancel
2100 if (!Status(_("Sending payment...")))
2104 if (!CommitTransaction(wtx, key))
2106 Error(_("The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."));
2110 // Send payment tx to seller, with response going to OnReply3 via event handler
2111 CWalletTx wtxSend = wtx;
2112 wtxSend.fFromMe = false;
2113 pnode->PushRequest("submitorder", wtxSend, SendingDialogOnReply3, this);
2115 Status(_("Waiting for confirmation..."));
2120 void SendingDialogOnReply3(void* parg, CDataStream& vRecv)
2122 ((CSendingDialog*)parg)->OnReply3(vRecv);
2125 void CSendingDialog::OnReply3(CDataStream& vRecv)
2133 Error(_("The payment was sent, but the recipient was unable to verify it.\n"
2134 "The transaction is recorded and will credit to the recipient,\n"
2135 "but the comment information will be blank."));
2141 //// what do we want to do about this?
2142 Error(_("Payment was sent, but an invalid response was received"));
2148 Status(_("Payment completed"));
2156 //////////////////////////////////////////////////////////////////////////////
2158 // CAddressBookDialog
2161 CAddressBookDialog::CAddressBookDialog(wxWindow* parent, const wxString& strInitSelected, int nPageIn, bool fDuringSendIn) : CAddressBookDialogBase(parent)
2163 // Set initially selected page
2164 wxNotebookEvent event;
2165 event.SetSelection(nPageIn);
2166 OnNotebookPageChanged(event);
2167 m_notebook->ChangeSelection(nPageIn);
2169 fDuringSend = fDuringSendIn;
2171 m_buttonCancel->Show(false);
2174 wxIcon iconAddressBook;
2175 iconAddressBook.CopyFromBitmap(wxBitmap(addressbook16_xpm));
2176 SetIcon(iconAddressBook);
2178 // Init column headers
2179 m_listCtrlSending->InsertColumn(0, _("Name"), wxLIST_FORMAT_LEFT, 200);
2180 m_listCtrlSending->InsertColumn(1, _("Address"), wxLIST_FORMAT_LEFT, 350);
2181 m_listCtrlSending->SetFocus();
2182 m_listCtrlReceiving->InsertColumn(0, _("Label"), wxLIST_FORMAT_LEFT, 200);
2183 m_listCtrlReceiving->InsertColumn(1, _("Bitcoin Address"), wxLIST_FORMAT_LEFT, 350);
2184 m_listCtrlReceiving->SetFocus();
2186 // Fill listctrl with address book data
2187 CRITICAL_BLOCK(cs_mapKeys)
2188 CRITICAL_BLOCK(cs_mapAddressBook)
2190 string strDefaultReceiving = (string)pframeMain->m_textCtrlAddress->GetValue();
2191 foreach(const PAIRTYPE(string, string)& item, mapAddressBook)
2193 string strAddress = item.first;
2194 string strName = item.second;
2196 bool fMine = (AddressToHash160(strAddress, hash160) && mapPubKeys.count(hash160));
2197 wxListCtrl* plistCtrl = fMine ? m_listCtrlReceiving : m_listCtrlSending;
2198 int nIndex = InsertLine(plistCtrl, strName, strAddress);
2199 if (strAddress == (fMine ? strDefaultReceiving : string(strInitSelected)))
2200 plistCtrl->SetItemState(nIndex, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
2205 wxString CAddressBookDialog::GetSelectedAddress()
2207 int nIndex = GetSelection(m_listCtrl);
2210 return GetItemText(m_listCtrl, nIndex, 1);
2213 wxString CAddressBookDialog::GetSelectedSendingAddress()
2215 int nIndex = GetSelection(m_listCtrlSending);
2218 return GetItemText(m_listCtrlSending, nIndex, 1);
2221 wxString CAddressBookDialog::GetSelectedReceivingAddress()
2223 int nIndex = GetSelection(m_listCtrlReceiving);
2226 return GetItemText(m_listCtrlReceiving, nIndex, 1);
2229 void CAddressBookDialog::OnNotebookPageChanged(wxNotebookEvent& event)
2232 nPage = event.GetSelection();
2233 if (nPage == SENDING)
2234 m_listCtrl = m_listCtrlSending;
2235 else if (nPage == RECEIVING)
2236 m_listCtrl = m_listCtrlReceiving;
2237 m_buttonDelete->Show(nPage == SENDING);
2238 m_buttonCopy->Show(nPage == RECEIVING);
2240 m_listCtrl->SetFocus();
2243 void CAddressBookDialog::OnListEndLabelEdit(wxListEvent& event)
2245 // Update address book with edited name
2247 if (event.IsEditCancelled())
2249 string strAddress = (string)GetItemText(m_listCtrl, event.GetIndex(), 1);
2250 SetAddressBookName(strAddress, string(event.GetText()));
2251 pframeMain->RefreshListCtrl();
2254 void CAddressBookDialog::OnListItemSelected(wxListEvent& event)
2257 if (nPage == RECEIVING)
2258 SetDefaultReceivingAddress((string)GetSelectedReceivingAddress());
2261 void CAddressBookDialog::OnListItemActivated(wxListEvent& event)
2266 // Doubleclick returns selection
2267 EndModal(GetSelectedAddress() != "" ? 2 : 0);
2271 // Doubleclick edits item
2272 wxCommandEvent event2;
2273 OnButtonEdit(event2);
2276 void CAddressBookDialog::OnButtonDelete(wxCommandEvent& event)
2278 if (nPage != SENDING)
2280 for (int nIndex = m_listCtrl->GetItemCount()-1; nIndex >= 0; nIndex--)
2282 if (m_listCtrl->GetItemState(nIndex, wxLIST_STATE_SELECTED))
2284 string strAddress = (string)GetItemText(m_listCtrl, nIndex, 1);
2285 CWalletDB().EraseName(strAddress);
2286 m_listCtrl->DeleteItem(nIndex);
2289 pframeMain->RefreshListCtrl();
2292 void CAddressBookDialog::OnButtonCopy(wxCommandEvent& event)
2294 // Copy address box to clipboard
2295 if (wxTheClipboard->Open())
2297 wxTheClipboard->SetData(new wxTextDataObject(GetSelectedAddress()));
2298 wxTheClipboard->Close();
2302 bool CAddressBookDialog::CheckIfMine(const string& strAddress, const string& strTitle)
2305 bool fMine = (AddressToHash160(strAddress, hash160) && mapPubKeys.count(hash160));
2307 wxMessageBox(_("This is one of your own addresses for receiving payments and cannot be entered in the address book. "), strTitle);
2311 void CAddressBookDialog::OnButtonEdit(wxCommandEvent& event)
2313 int nIndex = GetSelection(m_listCtrl);
2316 string strName = (string)m_listCtrl->GetItemText(nIndex);
2317 string strAddress = (string)GetItemText(m_listCtrl, nIndex, 1);
2318 string strAddressOrg = strAddress;
2320 if (nPage == SENDING)
2322 // Ask name and address
2325 CGetTextFromUserDialog dialog(this, _("Edit Address"), _("Name"), strName, _("Address"), strAddress);
2326 if (!dialog.ShowModal())
2328 strName = dialog.GetValue1();
2329 strAddress = dialog.GetValue2();
2331 while (CheckIfMine(strAddress, _("Edit Address")));
2334 else if (nPage == RECEIVING)
2337 CGetTextFromUserDialog dialog(this, _("Edit Address Label"), _("Label"), strName);
2338 if (!dialog.ShowModal())
2340 strName = dialog.GetValue();
2344 if (strAddress != strAddressOrg)
2345 CWalletDB().EraseName(strAddressOrg);
2346 SetAddressBookName(strAddress, strName);
2347 m_listCtrl->SetItem(nIndex, 1, strAddress);
2348 m_listCtrl->SetItemText(nIndex, strName);
2349 pframeMain->RefreshListCtrl();
2352 void CAddressBookDialog::OnButtonNew(wxCommandEvent& event)
2357 if (nPage == SENDING)
2359 // Ask name and address
2362 CGetTextFromUserDialog dialog(this, _("Add Address"), _("Name"), strName, _("Address"), strAddress);
2363 if (!dialog.ShowModal())
2365 strName = dialog.GetValue1();
2366 strAddress = dialog.GetValue2();
2368 while (CheckIfMine(strAddress, _("Add Address")));
2370 else if (nPage == RECEIVING)
2373 CGetTextFromUserDialog dialog(this,
2374 _("New Receiving Address"),
2375 _("You should use a new address for each payment you receive.\n\nLabel"),
2377 if (!dialog.ShowModal())
2379 strName = dialog.GetValue();
2382 strAddress = PubKeyToAddress(GenerateNewKey());
2385 // Add to list and select it
2386 SetAddressBookName(strAddress, strName);
2387 int nIndex = InsertLine(m_listCtrl, strName, strAddress);
2388 SetSelection(m_listCtrl, nIndex);
2389 m_listCtrl->SetFocus();
2390 if (nPage == SENDING)
2391 pframeMain->RefreshListCtrl();
2394 void CAddressBookDialog::OnButtonOK(wxCommandEvent& event)
2397 EndModal(GetSelectedAddress() != "" ? 1 : 0);
2400 void CAddressBookDialog::OnButtonCancel(wxCommandEvent& event)
2406 void CAddressBookDialog::OnClose(wxCloseEvent& event)
2417 //////////////////////////////////////////////////////////////////////////////
2424 ID_TASKBAR_RESTORE = 10001,
2426 ID_TASKBAR_GENERATE,
2430 BEGIN_EVENT_TABLE(CMyTaskBarIcon, wxTaskBarIcon)
2431 EVT_TASKBAR_LEFT_DCLICK(CMyTaskBarIcon::OnLeftButtonDClick)
2432 EVT_MENU(ID_TASKBAR_RESTORE, CMyTaskBarIcon::OnMenuRestore)
2433 EVT_MENU(ID_TASKBAR_OPTIONS, CMyTaskBarIcon::OnMenuOptions)
2434 EVT_MENU(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnMenuGenerate)
2435 EVT_UPDATE_UI(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnUpdateUIGenerate)
2436 EVT_MENU(ID_TASKBAR_EXIT, CMyTaskBarIcon::OnMenuExit)
2439 void CMyTaskBarIcon::Show(bool fShow)
2441 static char pszPrevTip[200];
2444 string strTooltip = _("Bitcoin");
2445 if (fGenerateBitcoins)
2446 strTooltip = _("Bitcoin - Generating");
2447 if (fGenerateBitcoins && vNodes.empty())
2448 strTooltip = _("Bitcoin - (not connected)");
2450 // Optimization, only update when changed, using char array to be reentrant
2451 if (strncmp(pszPrevTip, strTooltip.c_str(), sizeof(pszPrevTip)-1) != 0)
2453 strlcpy(pszPrevTip, strTooltip.c_str(), sizeof(pszPrevTip));
2455 // somehow it'll choose the wrong size and scale it down if
2456 // we use the main icon, so we hand it one with only 16x16
2457 SetIcon(wxICON(favicon), strTooltip);
2459 SetIcon(bitcoin80_xpm, strTooltip);
2465 strlcpy(pszPrevTip, "", sizeof(pszPrevTip));
2470 void CMyTaskBarIcon::Hide()
2475 void CMyTaskBarIcon::OnLeftButtonDClick(wxTaskBarIconEvent& event)
2480 void CMyTaskBarIcon::OnMenuRestore(wxCommandEvent& event)
2485 void CMyTaskBarIcon::OnMenuOptions(wxCommandEvent& event)
2487 // Since it's modal, get the main window to do it
2488 wxCommandEvent event2(wxEVT_COMMAND_MENU_SELECTED, wxID_PREFERENCES);
2489 pframeMain->GetEventHandler()->AddPendingEvent(event2);
2492 void CMyTaskBarIcon::Restore()
2495 wxIconizeEvent event(0, false);
2496 pframeMain->GetEventHandler()->AddPendingEvent(event);
2497 pframeMain->Iconize(false);
2498 pframeMain->Raise();
2501 void CMyTaskBarIcon::OnMenuGenerate(wxCommandEvent& event)
2503 GenerateBitcoins(event.IsChecked());
2506 void CMyTaskBarIcon::OnUpdateUIGenerate(wxUpdateUIEvent& event)
2508 event.Check(fGenerateBitcoins);
2511 void CMyTaskBarIcon::OnMenuExit(wxCommandEvent& event)
2513 pframeMain->Close(true);
2516 void CMyTaskBarIcon::UpdateTooltip()
2518 if (IsIconInstalled())
2522 wxMenu* CMyTaskBarIcon::CreatePopupMenu()
2524 wxMenu* pmenu = new wxMenu;
2525 pmenu->Append(ID_TASKBAR_RESTORE, _("&Open Bitcoin"));
2526 pmenu->Append(ID_TASKBAR_OPTIONS, _("O&ptions..."));
2527 pmenu->AppendCheckItem(ID_TASKBAR_GENERATE, _("&Generate Coins"))->Check(fGenerateBitcoins);
2528 #ifndef __WXMAC_OSX__ // Mac has built-in quit menu
2529 pmenu->AppendSeparator();
2530 pmenu->Append(ID_TASKBAR_EXIT, _("E&xit"));
2547 void CreateMainWindow()
2549 pframeMain = new CMainFrame(NULL);
2550 if (mapArgs.count("-min"))
2551 pframeMain->Iconize(true);
2553 if (!mapArgs.count("-minimizetotray"))
2554 fMinimizeToTray = false;
2556 pframeMain->Show(true); // have to show first to get taskbar button to hide
2557 if (fMinimizeToTray && pframeMain->IsIconized())
2558 fClosedToTray = true;
2559 pframeMain->Show(!fClosedToTray);
2560 ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
2561 CreateThread(ThreadDelayedRepaint, NULL);