1 // This file is part of BOINC.
2 // http://boinc.berkeley.edu
3 // Copyright (C) 2015 University of California
4 //
5 // BOINC is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU Lesser General Public License
7 // as published by the Free Software Foundation,
8 // either version 3 of the License, or (at your option) any later version.
9 //
10 // BOINC is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 // See the GNU Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public License
16 // along with BOINC.  If not, see <http://www.gnu.org/licenses/>.
17 
18 #if defined(__GNUG__) && !defined(__APPLE__)
19 #pragma implementation "BOINCListCtrl.h"
20 #endif
21 
22 #include "stdwx.h"
23 #include "BOINCBaseView.h"
24 #include "BOINCListCtrl.h"
25 #include "Events.h"
26 
27 
28 #ifndef wxHAS_LISTCTRL_COLUMN_ORDER
29 #define GetColumnOrder(x) x
30 #define GetColumnIndexFromOrder(x) x
31 #endif
32 
BEGIN_EVENT_TABLE(MyEvtHandler,wxEvtHandler)33 BEGIN_EVENT_TABLE(MyEvtHandler, wxEvtHandler)
34     EVT_PAINT(MyEvtHandler::OnPaint)
35 END_EVENT_TABLE()
36 
37 
38 IMPLEMENT_DYNAMIC_CLASS(MyEvtHandler, wxEvtHandler)
39 
40 MyEvtHandler::MyEvtHandler() {}
41 
MyEvtHandler(CBOINCListCtrl * theListControl)42 MyEvtHandler::MyEvtHandler(CBOINCListCtrl *theListControl) {
43     m_listCtrl = theListControl;
44 #ifdef __WXGTK__
45     m_view_startX = 0;
46 #endif
47 }
48 
49 
50 DEFINE_EVENT_TYPE(wxEVT_CHECK_SELECTION_CHANGED)
51 
52 #if USE_NATIVE_LISTCONTROL
DEFINE_EVENT_TYPE(wxEVT_DRAW_PROGRESSBAR)53 DEFINE_EVENT_TYPE(wxEVT_DRAW_PROGRESSBAR)
54 #endif
55 
56 BEGIN_EVENT_TABLE(CBOINCListCtrl, LISTCTRL_BASE)
57 
58 #if USE_NATIVE_LISTCONTROL
59     EVT_DRAW_PROGRESSBAR(CBOINCListCtrl::OnDrawProgressBar)
60 #else
61 #ifdef __WXMAC__
62 	EVT_SIZE(CBOINCListCtrl::OnSize)    // In MacAccessibility.mm
63 #endif
64 #endif
65 
66 #if ! USE_LIST_CACHE_HINT
67     EVT_LEFT_DOWN(CBOINCListCtrl::OnMouseDown)
68 #endif
69 END_EVENT_TABLE()
70 
71 
72 IMPLEMENT_DYNAMIC_CLASS(CBOINCListCtrl, LISTCTRL_BASE)
73 
74 
75 CBOINCListCtrl::CBOINCListCtrl() {}
76 
77 
CBOINCListCtrl(CBOINCBaseView * pView,wxWindowID iListWindowID,wxInt32 iListWindowFlags)78 CBOINCListCtrl::CBOINCListCtrl(
79     CBOINCBaseView* pView, wxWindowID iListWindowID, wxInt32 iListWindowFlags
80 ) : LISTCTRL_BASE(
81     pView, iListWindowID, wxDefaultPosition, wxSize(-1, -1), iListWindowFlags
82 ) {
83     m_pParentView = pView;
84 
85     // Enable Zebra Striping
86     EnableAlternateRowColours(true);
87 
88 #if USE_NATIVE_LISTCONTROL
89     m_bProgressBarEventPending = false;
90     PushEventHandler(new MyEvtHandler(this));
91 #else
92     savedHandler = GetMainWin()->GetEventHandler();
93     GetMainWin()->PushEventHandler(new MyEvtHandler(this));
94 #ifdef __WXMAC__
95     SetupMacAccessibilitySupport();
96 #endif
97 #endif
98 }
99 
100 
~CBOINCListCtrl()101 CBOINCListCtrl::~CBOINCListCtrl()
102 {
103 #if USE_NATIVE_LISTCONTROL
104     PopEventHandler(true);
105 #else
106     GetMainWin()->PopEventHandler(true);
107 #ifdef __WXMAC__
108     RemoveMacAccessibilitySupport();
109 #endif
110 #endif
111 
112     m_iRowsNeedingProgressBars.Clear();
113 }
114 
115 
OnSaveState(wxConfigBase * pConfig)116 bool CBOINCListCtrl::OnSaveState(wxConfigBase* pConfig) {
117     wxString    strBaseConfigLocation = wxEmptyString;
118     wxInt32     iIndex = 0;
119     wxInt32     iStdColumnCount = 0;
120     wxInt32     iActualColumnCount = GetColumnCount();
121     int         i, j;
122 
123     wxASSERT(pConfig);
124 
125     // Retrieve the base location to store configuration information
126     // Should be in the following form: "/Projects/"
127     strBaseConfigLocation = pConfig->GetPath() + wxT("/");
128 
129     iStdColumnCount = m_pParentView->m_iStdColWidthOrder.size();
130 
131     // Cycle through the columns recording their widths
132     for (iIndex = 0; iIndex < iActualColumnCount; iIndex++) {
133         m_pParentView->m_iStdColWidthOrder[m_pParentView->m_iColumnIndexToColumnID[iIndex]] = GetColumnWidth(iIndex);
134     }
135 
136     for (iIndex = 0; iIndex < iStdColumnCount; iIndex++) {
137         pConfig->SetPath(strBaseConfigLocation + m_pParentView->m_aStdColNameOrder->Item(iIndex));
138         pConfig->Write(wxT("Width"), m_pParentView->m_iStdColWidthOrder[iIndex]);
139     }
140 
141     // Save sorting column and direction
142     pConfig->SetPath(strBaseConfigLocation);
143     pConfig->Write(wxT("SortColumn"), m_pParentView->m_iSortColumnID);
144     pConfig->Write(wxT("ReverseSortOrder"), m_pParentView->m_bReverseSort);
145 
146     // Save Column Order
147     wxString strColumnOrder;
148     wxString strBuffer;
149     wxString strHiddenColumns;
150     wxArrayInt aOrder(iActualColumnCount);
151     CBOINCBaseView* pView = (CBOINCBaseView*)GetParent();
152     wxASSERT(wxDynamicCast(pView, CBOINCBaseView));
153 
154 #ifdef wxHAS_LISTCTRL_COLUMN_ORDER
155     aOrder = GetColumnsOrder();
156 #else
157     for (i = 0; i < iActualColumnCount; ++i) {
158         aOrder[i] = i;
159     }
160 #endif
161 
162     strColumnOrder.Printf(wxT("%s"), pView->m_aStdColNameOrder->Item(pView->m_iColumnIndexToColumnID[aOrder[0]]));
163 
164     for (i = 1; i < iActualColumnCount; ++i)
165     {
166         strBuffer.Printf(wxT(";%s"), pView->m_aStdColNameOrder->Item(pView->m_iColumnIndexToColumnID[aOrder[i]]));
167         strColumnOrder += strBuffer;
168     }
169 
170     pConfig->Write(wxT("ColumnOrder"), strColumnOrder);
171 
172     strHiddenColumns = wxEmptyString;
173     for (i = 0; i < iStdColumnCount; ++i) {
174         bool found = false;
175         for (j = 0; j < iActualColumnCount; ++j) {
176             if (pView->m_iColumnIndexToColumnID[aOrder[j]] == i) {
177                 found = true;
178                 break;
179             }
180         }
181         if (found) continue;
182         if (!strHiddenColumns.IsEmpty()) {
183             strHiddenColumns += wxT(";");
184         }
185         strHiddenColumns += pView->m_aStdColNameOrder->Item(i);
186     }
187     pConfig->Write(wxT("HiddenColumns"), strHiddenColumns);
188 
189     return true;
190 }
191 
192 
OnRestoreState(wxConfigBase * pConfig)193 bool CBOINCListCtrl::OnRestoreState(wxConfigBase* pConfig) {
194     wxString    strBaseConfigLocation = wxEmptyString;
195     wxInt32     iIndex = 0;
196     wxInt32     iStdColumnCount = 0;
197     wxInt32     iTempValue = 0;
198 
199     wxASSERT(pConfig);
200 
201     // Retrieve the base location to store configuration information
202     // Should be in the following form: "/Projects/"
203     strBaseConfigLocation = pConfig->GetPath() + wxT("/");
204 
205     iStdColumnCount = m_pParentView->m_iStdColWidthOrder.size();
206 
207     // Cycle through the possible columns updating column widths
208     for (iIndex = 0; iIndex < iStdColumnCount; iIndex++) {
209         pConfig->SetPath(strBaseConfigLocation + m_pParentView->m_aStdColNameOrder->Item(iIndex));
210 
211         pConfig->Read(wxT("Width"), &iTempValue, -1);
212         if (-1 != iTempValue) {
213             m_pParentView->m_iStdColWidthOrder[iIndex] = iTempValue;
214         }
215     }
216 
217     // Restore sorting column and direction
218     pConfig->SetPath(strBaseConfigLocation);
219     pConfig->Read(wxT("ReverseSortOrder"), &iTempValue,-1);
220     if (-1 != iTempValue) {
221             m_pParentView->m_bReverseSort = iTempValue != 0 ? true : false;
222     }
223     pConfig->Read(wxT("SortColumn"), &iTempValue,-1);
224     if (-1 != iTempValue) {
225         m_pParentView->m_iSortColumnID = iTempValue;
226     }
227 
228     // Restore Column Order
229     wxString strColumnOrder;
230     wxString strHiddenColumns;
231     CBOINCBaseView* pView = (CBOINCBaseView*)GetParent();
232 
233     if (pConfig->Read(wxT("ColumnOrder"), &strColumnOrder)) {
234         wxArrayString orderArray;
235         TokenizedStringToArray(strColumnOrder, ";", &orderArray);
236         SetListColumnOrder(orderArray);
237 
238         // If the user installed a new vesion of BOINC, new columns may have
239         // been added that didn't exist in the older version. Check for this.
240         //
241         // This will also be triggered if the locale is changed, which will cause
242         // SetListColumnOrder() to be called again so the wxListCtrl will be set
243         // up with the correctly labeled columns.
244         bool foundNewColumns = false;
245 
246         if (pConfig->Read(wxT("HiddenColumns"), &strHiddenColumns)) {
247             wxArrayString hiddenArray;
248             TokenizedStringToArray(strHiddenColumns, ";", &hiddenArray);
249             int shownCount = orderArray.size();
250             int hiddenCount = hiddenArray.size();
251             int totalCount = pView->m_aStdColNameOrder->size();
252             for (int i = 0; i < totalCount; ++i) {
253                 wxString columnNameToFind = pView->m_aStdColNameOrder->Item(i);
254                 bool found = false;
255                 for (int j = 0; j < shownCount; ++j) {
256                     if (orderArray[j].IsSameAs(columnNameToFind)) {
257                         found = true;
258                         break;
259                     }
260                 }
261                 if (found) continue;
262 
263                 for (int j = 0; j < hiddenCount; ++j) {
264                     if (hiddenArray[j].IsSameAs(columnNameToFind)) {
265                         found = true;
266                         break;
267                     }
268                 }
269                 if (found) continue;
270 
271                 foundNewColumns =  true;
272                 orderArray.Add(columnNameToFind);
273             }
274         }
275         if (foundNewColumns) {
276             bool wasInStandardOrder = IsColumnOrderStandard();
277             SetListColumnOrder(orderArray);
278             if (wasInStandardOrder) SetStandardColumnOrder();
279         }
280     } else {
281         // No "ColumnOrder" tag in pConfig
282         // Show all columns in default column order
283         wxASSERT(wxDynamicCast(pView, CBOINCBaseView));
284 
285         SetDefaultColumnDisplay();
286     }
287 
288     if (m_pParentView->m_iSortColumnID != -1) {
289         m_pParentView->InitSort();
290     }
291 
292     return true;
293 }
294 
295 
TokenizedStringToArray(wxString tokenized,char * delimiters,wxArrayString * array)296 void CBOINCListCtrl::TokenizedStringToArray(wxString tokenized, char * delimiters, wxArrayString* array) {
297     wxString name;
298 
299     array->Clear();
300     wxStringTokenizer tok(tokenized, delimiters);
301     while (tok.HasMoreTokens())
302     {
303         name = tok.GetNextToken();
304         if (name.IsEmpty()) continue;
305         array->Add(name);
306     }
307 }
308 
309 
310 // SetListColumnOrder() is called mostly from OnRestoreState(), so we don't
311 // call OnSaveState() from here.  CDlgHiddenColumns calls OnSaveState()
312 // when we really need to do that.
313 //
314 // Unfortunately, we have no way of immediately calling OnSaveState() when
315 // the user manually reorders columns because that does not generate a
316 // notification from MS Windows so wxWidgets can't generate an event.
SetListColumnOrder(wxArrayString & orderArray)317 void CBOINCListCtrl::SetListColumnOrder(wxArrayString& orderArray) {
318     int i, stdCount, columnPosition;
319     int colCount = GetColumnCount();
320     int shownColCount = orderArray.GetCount();
321     int columnIndex = 0;    // Column number among shown columns before re-ordering
322     int columnID = 0;       // ID of column, e.g. COLUMN_PROJECT, COLUMN_STATUS, etc.
323     int sortColumnIndex = -1;
324     wxArrayInt aOrder(shownColCount);
325 
326     CBOINCBaseView* pView = (CBOINCBaseView*)GetParent();
327     wxASSERT(wxDynamicCast(pView, CBOINCBaseView));
328 
329     pView->m_iColumnIndexToColumnID.Clear();
330     for (i=colCount-1; i>=0; --i) {
331         DeleteColumn(i);
332     }
333 
334     stdCount = pView->m_aStdColNameOrder->GetCount();
335 
336     pView->m_iColumnIDToColumnIndex.Clear();
337     for (columnID=0; columnID<stdCount; ++columnID) {
338         pView->m_iColumnIDToColumnIndex.Add(-1);
339     }
340 
341     for (columnID=0; columnID<stdCount; ++columnID) {
342         for (columnPosition=0; columnPosition<shownColCount; ++columnPosition) {
343             if (orderArray[columnPosition].IsSameAs(pView->m_aStdColNameOrder->Item(columnID))) {
344                 aOrder[columnPosition] = columnIndex;
345                 pView->AppendColumn(columnID);
346                 pView->m_iColumnIndexToColumnID.Add(columnID);
347                 pView->m_iColumnIDToColumnIndex[columnID] = columnIndex;
348 
349                 ++columnIndex;
350                 break;
351             }
352         }
353     }
354 
355     // Prevent a crash bug if we just changed to a new locale.
356     //
357     // If a column has the same name in both the old and new locale, we guard against
358     // changing the sort column to that column.
359     //
360     // CBOINCListCtrl::OnRestoreState() may have incorrectly added the column names in
361     // the new locale as "new" columns, so check against both shownColCount and stdCount.
362     int limit = wxMin(shownColCount, stdCount);
363     if (columnIndex < limit) {
364         SetStandardColumnOrder();
365         for (columnID=0; columnID<limit; ++columnID) {
366             aOrder[columnID] = columnID;
367             pView->AppendColumn(columnID);
368             pView->m_iColumnIndexToColumnID.Add(columnID);
369             pView->m_iColumnIDToColumnIndex[columnID] = columnID;
370         }
371     }
372 
373     // If sort column is now hidden, set the new first column as sort column
374     if (pView->m_iSortColumnID >= 0) {
375         sortColumnIndex = pView->m_iColumnIDToColumnIndex[pView->m_iSortColumnID];
376         if (sortColumnIndex < 0) {
377             pView->m_iSortColumnID = pView->m_iColumnIndexToColumnID[0];
378             pView->m_bReverseSort = false;
379             pView->SetSortColumn(0);
380         } else {
381             // Redraw the sort arrow, etc.
382             pView->SetSortColumn(sortColumnIndex);
383         }
384     }
385 
386 #ifdef wxHAS_LISTCTRL_COLUMN_ORDER
387     colCount = GetColumnCount();
388     if ((shownColCount > 0) && (shownColCount <= stdCount) && (colCount == shownColCount)) {
389         SetColumnsOrder(aOrder);
390     }
391 #endif
392 }
393 
394 
IsColumnOrderStandard()395 bool CBOINCListCtrl::IsColumnOrderStandard() {
396 #ifdef wxHAS_LISTCTRL_COLUMN_ORDER
397     int i;
398     wxArrayInt aOrder = GetColumnsOrder();
399     int orderCount = aOrder.GetCount();
400     for (i=1; i<orderCount; ++i) {
401         if(aOrder[i] < aOrder[i-1]) return false;
402     }
403 #endif
404     return true;
405 }
406 
407 
SetStandardColumnOrder()408 void CBOINCListCtrl::SetStandardColumnOrder() {
409     int i;
410     int colCount = GetColumnCount();
411     wxArrayInt aOrder(colCount);
412 
413     for (i=0; i<colCount; ++i) {
414         aOrder[i] = i;
415     }
416 #ifdef wxHAS_LISTCTRL_COLUMN_ORDER
417     if (colCount) {
418         SetColumnsOrder(aOrder);
419     }
420 #endif
421 }
422 
423 
SetDefaultColumnDisplay()424 void CBOINCListCtrl::SetDefaultColumnDisplay() {
425     int i;
426     wxArrayString orderArray;
427     CBOINCBaseView* pView = (CBOINCBaseView*)GetParent();
428 
429     wxASSERT(wxDynamicCast(pView, CBOINCBaseView));
430 
431     orderArray.Clear();
432     for (i=0; i<pView->m_iNumDefaultShownColumns; ++i) {
433         orderArray.Add(pView->m_aStdColNameOrder->Item(pView->m_iDefaultShownColumns[i]));
434     }
435 
436     SetListColumnOrder(orderArray);
437     SetStandardColumnOrder();
438 }
439 
440 
SelectRow(int row,bool setSelected)441 void CBOINCListCtrl::SelectRow(int row, bool setSelected) {
442     SetItemState(row,  setSelected ? wxLIST_STATE_SELECTED : 0, wxLIST_STATE_SELECTED);
443 }
444 
445 
AddPendingProgressBar(int row)446 void CBOINCListCtrl::AddPendingProgressBar(int row) {
447     bool duplicate = false;
448     int n = (int)m_iRowsNeedingProgressBars.GetCount();
449     for (int i=0; i<n; ++i) {
450         if (m_iRowsNeedingProgressBars[i] == row) {
451             duplicate = true;
452         }
453     }
454     if (!duplicate) {
455         m_iRowsNeedingProgressBars.Add(row);
456     }
457 }
458 
459 
OnGetItemText(long item,long column) const460 wxString CBOINCListCtrl::OnGetItemText(long item, long column) const {
461     wxASSERT(m_pParentView);
462     wxASSERT(wxDynamicCast(m_pParentView, CBOINCBaseView));
463 
464     return m_pParentView->FireOnListGetItemText(item, column);
465 }
466 
467 
OnGetItemImage(long item) const468 int CBOINCListCtrl::OnGetItemImage(long item) const {
469     wxASSERT(m_pParentView);
470     wxASSERT(wxDynamicCast(m_pParentView, CBOINCBaseView));
471 
472     return m_pParentView->FireOnListGetItemImage(item);
473 }
474 
475 
DrawProgressBars()476 void CBOINCListCtrl::DrawProgressBars()
477 {
478     long topItem, numItems, numVisibleItems, row;
479     wxRect r, rr;
480     int w = 0, x = 0, xx, yy, ww;
481     int progressColumn = -1;
482 
483     if (m_pParentView->GetProgressColumn() >= 0) {
484         progressColumn = m_pParentView->m_iColumnIDToColumnIndex[m_pParentView->GetProgressColumn()];
485     }
486 
487 #if USE_NATIVE_LISTCONTROL
488     wxClientDC dc(this);
489     m_bProgressBarEventPending = false;
490 #else
491     wxClientDC dc(GetMainWin());   // Available only in wxGenericListCtrl
492 #endif
493 
494     if (progressColumn < 0) {
495         m_iRowsNeedingProgressBars.Clear();
496         return;
497     }
498 
499     int n = (int)m_iRowsNeedingProgressBars.GetCount();
500     if (n <= 0) return;
501 
502     wxColour progressColor = wxTheColourDatabase->Find(wxT("LIGHT BLUE"));
503     wxBrush progressBrush(progressColor);
504 
505     numItems = GetItemCount();
506     if (numItems) {
507         topItem = GetTopItem();     // Doesn't work properly for Mac Native control in wxMac-2.8.7
508 
509         numVisibleItems = GetCountPerPage();
510         ++numVisibleItems;
511 
512         if (numItems <= (topItem + numVisibleItems)) numVisibleItems = numItems - topItem;
513 
514         x = 0;
515         int progressColumnPosition = GetColumnOrder(progressColumn);
516         for (int i=0; i<progressColumnPosition; i++) {
517             x += GetColumnWidth(GetColumnIndexFromOrder(i));
518         }
519         w = GetColumnWidth(progressColumn);
520 
521 #if USE_NATIVE_LISTCONTROL
522         x -= GetScrollPos(wxHORIZONTAL);
523 #else
524         CalcScrolledPosition(x, 0, &x, &yy);
525 #endif
526         wxFont theFont = GetFont();
527         dc.SetFont(theFont);
528 
529         for (int i=0; i<n; ++i) {
530             row = m_iRowsNeedingProgressBars[i];
531             if (row < topItem) continue;
532             if (row > (topItem + numVisibleItems -1)) continue;
533 
534 
535             GetItemRect(row, r);
536 #if ! USE_NATIVE_LISTCONTROL
537             r.y = r.y - GetHeaderHeight() - 1;
538 #endif
539             r.x = x;
540             r.width = w;
541             r.Inflate(-1, -2);
542             rr = r;
543 
544             wxString progressString = m_pParentView->GetProgressText(row);
545             dc.GetTextExtent(progressString, &xx, &yy);
546 
547             r.y += (r.height - yy - 1) / 2;
548 
549             // Adapted from ellipis code in wxRendererGeneric::DrawHeaderButtonContents()
550             if (xx > r.width) {
551                 int ellipsisWidth;
552                 dc.GetTextExtent( wxT("..."), &ellipsisWidth, NULL);
553                 if (ellipsisWidth > r.width) {
554                     progressString.Clear();
555                     xx = 0;
556                 } else {
557                     do {
558                         progressString.Truncate( progressString.length() - 1 );
559                         dc.GetTextExtent( progressString, &xx, &yy);
560                     } while (xx + ellipsisWidth > r.width && progressString.length() );
561                     progressString.append( wxT("...") );
562                     xx += ellipsisWidth;
563                 }
564             }
565 
566             dc.SetLogicalFunction(wxCOPY);
567             dc.SetBackgroundMode(wxSOLID);
568             dc.SetPen(progressColor);
569             dc.SetBrush(progressBrush);
570             dc.DrawRectangle( rr );
571 
572             rr.Inflate(-2, -1);
573             ww = rr.width * m_pParentView->GetProgressValue(row);
574             rr.x += ww;
575             rr.width -= ww;
576 
577 #if 0
578             // Show background stripes behind progress bars
579             wxListItemAttr* attr = m_pParentView->FireOnListGetItemAttr(row);
580             wxColour bkgd = attr->GetBackgroundColour();
581             dc.SetPen(bkgd);
582             dc.SetBrush(bkgd);
583 #else
584             dc.SetPen(*wxWHITE_PEN);
585             dc.SetBrush(*wxWHITE_BRUSH);
586 #endif
587             dc.DrawRectangle( rr );
588 
589             dc.SetPen(*wxBLACK_PEN);
590             dc.SetBackgroundMode(wxTRANSPARENT);
591             if (xx > (r.width - 7)) {
592                 dc.DrawText(progressString, r.x, r.y);
593             } else {
594                 dc.DrawText(progressString, r.x + (w - 8 - xx), r.y);
595             }
596         }
597     }
598     m_iRowsNeedingProgressBars.Clear();
599 }
600 
601 #if USE_NATIVE_LISTCONTROL
602 
OnPaint(wxPaintEvent & event)603 void MyEvtHandler::OnPaint(wxPaintEvent & event)
604 {
605     event.Skip();
606     if (m_listCtrl) {
607         m_listCtrl->PostDrawProgressBarEvent();
608     }
609 }
610 
PostDrawProgressBarEvent()611 void CBOINCListCtrl::PostDrawProgressBarEvent() {
612     if (m_bProgressBarEventPending) return;
613 
614     CDrawProgressBarEvent newEvent(wxEVT_DRAW_PROGRESSBAR, this);
615     AddPendingEvent(newEvent);
616     m_bProgressBarEventPending = true;
617 }
618 
OnDrawProgressBar(CDrawProgressBarEvent & event)619 void CBOINCListCtrl::OnDrawProgressBar(CDrawProgressBarEvent& event) {
620     DrawProgressBars();
621     event.Skip();
622 }
623 
624 #else
625 
OnPaint(wxPaintEvent & event)626 void MyEvtHandler::OnPaint(wxPaintEvent & event)
627 {
628     if (m_listCtrl) {
629         m_listCtrl->savedHandler->ProcessEvent(event);
630         m_listCtrl->DrawProgressBars();
631 #ifdef __WXGTK__
632         // Work around a wxWidgets 3.0 bug in wxGenericListCtrl (Linux
633         // only) which causes headers to be misaligned after horizontal
634         // scrolling due to wxListHeaderWindow::OnPaint() calling
635         // parent->GetViewStart() before the parent window has been
636         // scrolled to the new position.
637         int view_startX;
638         m_listCtrl->GetViewStart( &view_startX, NULL );
639         if (view_startX != m_view_startX) {
640             m_view_startX = view_startX;
641             ((wxWindow *)m_listCtrl->m_headerWin)->Refresh();
642             ((wxWindow *)m_listCtrl->m_headerWin)->Update();
643         }
644 #endif
645     } else {
646         event.Skip();
647     }
648 }
649 
650 #endif
651 
652 
653 #if ! USE_LIST_CACHE_HINT
654 
655 // Work around features in multiple selection virtual wxListCtrl:
656 //  * It does not send deselection events (except ctrl-click).
657 //  * It does not send selection events if you add to selection
658 //    using Shift_Click.
659 //
660 // Post a special event.  This will allow this mouse event to
661 // propogate through the chain to complete any selection or
662 // deselection operatiion, then the special event will trigger
663 // CBOINCBaseView::OnCheckSelectionChanged() to respond to the
664 // selection change, if any.
665 //
OnMouseDown(wxMouseEvent & event)666 void CBOINCListCtrl::OnMouseDown(wxMouseEvent& event) {
667     CCheckSelectionChangedEvent newEvent(wxEVT_CHECK_SELECTION_CHANGED, this);
668     m_pParentView->GetEventHandler()->AddPendingEvent(newEvent);
669     event.Skip();
670 }
671 
672 #endif
673 
674 
675 // To reduce flicker, refresh only changed columns (except
676 // on Mac, which is double-buffered to eliminate flicker.)
RefreshCell(int row,int col)677 void CBOINCListCtrl::RefreshCell(int row, int col) {
678     wxRect r;
679 
680     GetSubItemRect(row, col, r);
681     RefreshRect(r);
682 }
683