// Berkeley Open Infrastructure for Network Computing // http://boinc.berkeley.edu // Copyright (C) 2005 University of California // // This is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; // either version 2.1 of the License, or (at your option) any later version. // // This software is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU Lesser General Public License for more details. // // To view the GNU Lesser General Public License visit // http://www.gnu.org/copyleft/lesser.html // or write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #if defined(__GNUG__) && !defined(__APPLE__) #pragma implementation "BOINCGridCtrl.h" #endif #include "stdwx.h" #include "BOINCGridCtrl.h" #include "BOINCBaseView.h" #include "res/sortascending.xpm" #include "res/sortdescending.xpm" /* static compare functions and helper variable */ static bool reverseCompareOrder=false; /* compare Strings that contains floats */ static int CompareFloatString(const wxString& first,const wxString& second) { double dFirst; double dSecond; first.ToDouble(&dFirst); second.ToDouble(&dSecond); if(dFirst < dSecond) { return reverseCompareOrder ? 1 : -1 ; } if(dSecond < dFirst) { return reverseCompareOrder ? -1 : 1 ; } return 0; } /* compare Strings case insensitive */ static int CompareStringStringNoCase(const wxString& first,const wxString& second) { int ret = first.CmpNoCase(second); return reverseCompareOrder ? ret * (-1) : ret; } /* compare Strings that contains int/long numbers */ static int CompareLongString(const wxString& first,const wxString& second) { long lFirst; long lSecond; first.ToLong(&lFirst); second.ToLong(&lSecond); if(lFirst < lSecond) { return reverseCompareOrder ? 1 : -1 ; } if(lSecond < lFirst) { return reverseCompareOrder ? -1 : 1 ; } return 0; } /* compare strings that contains time periods in format 00:00:00 */ static int CompareTimeString(const wxString& first,const wxString& second) { long dtFirst,dtSecond; wxString hours,minutes,seconds; long lHours,lMinutes,lSeconds; //converting the first string to long value hours = first.BeforeFirst(':'); seconds = first.AfterLast(':'); minutes = first.AfterFirst(':').BeforeFirst(':'); hours.ToLong(&lHours); minutes.ToLong(&lMinutes); seconds.ToLong(&lSeconds); dtFirst = lSeconds + lMinutes * 60 + lHours * 3600; //converting the second string hours = second.BeforeFirst(':'); seconds = second.AfterLast(':'); minutes = second.AfterFirst(':').BeforeFirst(':'); hours.ToLong(&lHours); minutes.ToLong(&lMinutes); seconds.ToLong(&lSeconds); dtSecond = lSeconds + lMinutes * 60 + lHours * 3600; if(dtFirst < dtSecond) { return reverseCompareOrder ? 1 : -1 ; } if(dtSecond < dtFirst) { return reverseCompareOrder ? -1 : 1 ; } return 0; } /* compare Strings that contains dateTime*/ static int CompareDateString(const wxString& first,const wxString& second) { wxDateTime dtFirst,dtSecond; //dtFirst.ParseDateTime(first); //dtSecond.ParseDateTime(second); dtFirst.ParseFormat(first,wxT(" %x %X")); dtSecond.ParseFormat(second,wxT(" %x %X")); if(dtFirst < dtSecond) { return reverseCompareOrder ? 1 : -1 ; } if(dtSecond < dtFirst) { return reverseCompareOrder ? -1 : 1 ; } return 0; } /* ########## grid ctrl implementation ############### */ BEGIN_EVENT_TABLE (CBOINCGridCtrl, wxGrid) EVT_GRID_LABEL_LEFT_CLICK(CBOINCGridCtrl::OnLabelLClick) END_EVENT_TABLE () /* Constructor, don't call any grid methods here, because they could raise events, which couldn't be handled correctly while the grid isn't constructed completly. Instead call Setup() and place all further initialization there */ CBOINCGridCtrl::CBOINCGridCtrl(wxWindow* parent, wxWindowID iGridWindowID) : wxGrid(parent, iGridWindowID) { //init members sortColumn=-1; sortAscending=true; sortNeededByLabelClick=false; m_pkColumnIndex1=-1; m_pkColumnIndex2=-1; m_cursorcol=-1; m_cursorrow=-1; m_arrSelectedKeys1.Clear(); m_arrSelectedKeys2.Clear(); //load sorting bitmaps ascBitmap = wxBitmap(sortascending_xpm); descBitmap = wxBitmap(sortdescending_xpm); } /* make settings for the grid here instead in the constructor */ void CBOINCGridCtrl::Setup() { //make grid cursor invisible SetCellHighlightPenWidth(0); SetCellHighlightROPenWidth(0); //change default selection colours SetSelectionBackground(*wxLIGHT_GREY); SetSelectionForeground(*wxBLACK); // SetRowLabelSize(1);//hide row labels SetColLabelSize(20); //make header row smaller as default SetColLabelAlignment(wxALIGN_LEFT,wxALIGN_CENTER); EnableGridLines(false); EnableDragRowSize(false);//prevent the user from changing the row height with the mouse EnableDragCell(false); EnableEditing(false);//make the whole grid read-only SetDefaultCellBackgroundColour(*wxWHITE); #ifdef __WXMAC__ SetLabelFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); SetDefaultCellFont(wxFont(12, wxSWISS, wxNORMAL, wxNORMAL, FALSE)); #else SetLabelFont(*wxNORMAL_FONT); #endif this->SetScrollLineX(5); this->SetScrollLineY(5); } CBOINCGridCtrl::~CBOINCGridCtrl() { } /* use this method instead of wxGrid::GetSelectedRows() because a bug in wxGrid::GetSelectedRows() doesn't return anything although some rows are selected */ wxArrayInt CBOINCGridCtrl::GetSelectedRows2() { wxArrayInt ret; for(int i= 0; i< this->GetRows();i++) { for(int j=0; j< this->GetCols();j++) { if(this->IsInSelection(i,j)) { ret.Add(i); //break the inner loop here to prevent adding the same row twice break; } } } return ret; } /* sets the column with unique values */ void CBOINCGridCtrl::SetPrimaryKeyColumns(int col1,int col2) { m_pkColumnIndex1 = col1; m_pkColumnIndex2 = col2; } /* If the user clicked inside the grid, the former selected cells must be deselected. Because the code in OnListRender() of the GridViews raises SelectionEvents too, call to GetBatchCount() here is necessary to decide what to do */ void CBOINCGridCtrl::ClearSavedSelection() { if(this->GetBatchCount()<=0) { m_arrSelectedKeys1.Empty(); m_arrSelectedKeys2.Empty(); } } /* save the key values of the currently selected rows for later restore */ void CBOINCGridCtrl::SaveSelection() { if(m_pkColumnIndex1>=0) { wxArrayInt arrSelRows = GetSelectedRows2(); m_arrSelectedKeys1.Empty(); m_arrSelectedKeys2.Empty(); for(unsigned int i=0; i< arrSelRows.GetCount();i++) { m_arrSelectedKeys1.Add(GetCellValue(arrSelRows[i],m_pkColumnIndex1)); if (m_pkColumnIndex2 >= 0) { m_arrSelectedKeys2.Add(GetCellValue(arrSelRows[i],m_pkColumnIndex2)); } } } } /* select all rows, that were formerly selected this raises selection events without user interaction */ void CBOINCGridCtrl::RestoreSelection() { BeginBatch(); // To prevent flicker ClearSelection(); for(unsigned int i=0;i < m_arrSelectedKeys1.size();i++) { int index = GetTable()->FindRowIndexByColValue( m_pkColumnIndex1,m_arrSelectedKeys1[i],m_pkColumnIndex2,m_arrSelectedKeys2[i] ); if(index >=0) { SelectRow(index,true); } } EndBatch(); } void CBOINCGridCtrl::SaveGridCursorPosition() { m_cursorcol = GetGridCursorCol(); m_cursorrow = GetGridCursorRow(); if(m_cursorrow>=0 && m_cursorcol >=0) { m_szCursorKey1 = GetCellValue(m_cursorrow,m_pkColumnIndex1); if (m_pkColumnIndex2 >= 0) { m_szCursorKey2 = GetCellValue(m_cursorrow,m_pkColumnIndex2); } } } void CBOINCGridCtrl::RestoreGridCursorPosition() { int index = GetTable()->FindRowIndexByColValue(m_pkColumnIndex1,m_szCursorKey1,m_pkColumnIndex2,m_szCursorKey2); if(index >=0) { SetGridCursor(index,m_cursorcol); } } int CBOINCGridCtrl::GetFirstSelectedRow() { int ret= -1; //Row mode ? wxArrayInt selRows = this->GetSelectedRows2(); if(selRows.size() >0) { return selRows[0]; } //block mode ? wxGridCellCoordsArray selBlocks = this->GetSelectionBlockTopLeft(); if(selBlocks.size()>0) { return selBlocks[0].GetRow(); } //cell mode wxGridCellCoordsArray selCells = this->GetSelectionBlockTopLeft(); if(selCells.size()>0) { return selCells[0].GetRow(); } return ret; } /* saves column widths and sorting attributes */ bool CBOINCGridCtrl::OnSaveState(wxConfigBase* pConfig) { wxString strBaseConfigLocation = wxEmptyString; wxInt32 iIndex = 0; wxInt32 iColumnCount = this->GetCols(); wxASSERT(pConfig); // Retrieve the base location to store configuration information // Should be in the following form: "/Projects/" strBaseConfigLocation = pConfig->GetPath() + wxT("/"); // Cycle through the columns recording anything interesting for (iIndex = 0; iIndex < iColumnCount; iIndex++) { wxString label = this->GetColLabelValue(iIndex); // Don't save width for hidden / invisible columns if (label.IsEmpty()) { continue; } pConfig->SetPath(strBaseConfigLocation + label); pConfig->Write(wxT("Width"), this->GetColumnWidth(iIndex)); } //save sorting column pConfig->SetPath(strBaseConfigLocation); pConfig->Write(wxT("SortColumn"),this->sortColumn); pConfig->Write(wxT("SortAscending"),this->sortAscending); return true; } /* restores column widths and sorting attributes */ bool CBOINCGridCtrl::OnRestoreState(wxConfigBase* pConfig) { wxString strBaseConfigLocation = wxEmptyString; wxInt32 iIndex = 0; wxInt32 iTempValue = 0; wxInt32 iColumnCount = GetCols(); wxASSERT(pConfig); // Retrieve the base location to store configuration information // Should be in the following form: "/Projects/" strBaseConfigLocation = pConfig->GetPath() + wxT("/"); // Cycle through the columns recording anything interesting for (iIndex = 0; iIndex < iColumnCount; iIndex++) { wxString label = GetColLabelValue(iIndex); // Don't restore width for hidden / invisible columns if (label.IsEmpty()) { continue; } pConfig->SetPath(strBaseConfigLocation + label); pConfig->Read(wxT("Width"), &iTempValue, -1); if (-1 != iTempValue) { SetColSize(iIndex, iTempValue); } } //read sorting pConfig->SetPath(strBaseConfigLocation); pConfig->Read(wxT("SortColumn"),&iTempValue,-1); if(-1 != iTempValue) { sortColumn = iTempValue; } pConfig->Read(wxT("SortAscending"),&iTempValue,-1); if(-1 != iTempValue) { sortAscending = iTempValue != 0 ? true : false; } return true; } /* for convienience purpose only */ void CBOINCGridCtrl::SetColAlignment(int col,int hAlign,int vAlign) { wxGridCellAttr *attr = m_table->GetAttr(-1, col, wxGridCellAttr::Col ); if(!attr) { attr = new wxGridCellAttr; } attr->SetAlignment(hAlign,vAlign); SetColAttr(col, attr); } /* copied source code from wxGrid to make overloading possible */ void CBOINCGridCtrl::DrawTextRectangle( wxDC& dc, const wxString& value, const wxRect& rect, int horizAlign, int vertAlign, int textOrientation ) { wxArrayString lines; StringToLines( value, lines ); //Forward to new API. DrawTextRectangle( dc, lines, rect, horizAlign, vertAlign, textOrientation ); } /* draws text with ellipses instead cutting it */ void CBOINCGridCtrl::DrawTextRectangle( wxDC& dc, const wxArrayString& lines, const wxRect& rect, int horizAlign, int vertAlign, int textOrientation ) { long textWidth, textHeight; long lineWidth, lineHeight; unsigned int nLines; dc.SetClippingRegion( rect ); nLines = (unsigned int)lines.GetCount(); if( nLines > 0 ) { unsigned int l; float x = 0.0, y = 0.0; if( textOrientation == wxHORIZONTAL ) GetTextBoxSize(dc, lines, &textWidth, &textHeight); else GetTextBoxSize( dc, lines, &textHeight, &textWidth ); switch( vertAlign ) { case wxALIGN_BOTTOM: if( textOrientation == wxHORIZONTAL ) y = rect.y + (rect.height - textHeight - 1); else x = rect.x + rect.width - textWidth; break; case wxALIGN_CENTRE: if( textOrientation == wxHORIZONTAL ) y = rect.y + ((rect.height - textHeight)/2); else x = rect.x + ((rect.width - textWidth)/2); break; case wxALIGN_TOP: default: if( textOrientation == wxHORIZONTAL ) y = rect.y + 1; else x = rect.x + 1; break; } // Align each line of a multi-line label for( l = 0; l < nLines; l++ ) { dc.GetTextExtent(lines[l], &lineWidth, &lineHeight); switch( horizAlign ) { case wxALIGN_RIGHT: if( textOrientation == wxHORIZONTAL ) x = rect.x + (rect.width - lineWidth - 1); else y = rect.y + lineWidth + 1; break; case wxALIGN_CENTRE: if( textOrientation == wxHORIZONTAL ) x = rect.x + ((rect.width - lineWidth)/2); else y = rect.y + rect.height - ((rect.height - lineWidth)/2); break; case wxALIGN_LEFT: default: if( textOrientation == wxHORIZONTAL ) x = rect.x + 1; else y = rect.y + rect.height - 1; break; } if( textOrientation == wxHORIZONTAL ) { //changes applies here wxString formattedText = FormatTextWithEllipses(dc,lines[l],rect.width); dc.DrawText( formattedText, (int)x, (int)y ); y += lineHeight; } else { dc.DrawRotatedText( lines[l], (int)x, (int)y, 90.0 ); x += lineHeight; } } } dc.DestroyClippingRegion(); } wxString CBOINCGridCtrl::FormatTextWithEllipses(wxDC& dc,const wxString &text,int width) { wxString retText; wxString drawntext, ellipsis; wxCoord w, h, base_w; wxListItem item; // determine if the string can fit inside the current width dc.GetTextExtent(text, &w, &h); if (w <= width) { retText = text; } else // otherwise, truncate and add an ellipsis if possible { // determine the base width ellipsis = wxString(wxT("...")); dc.GetTextExtent(ellipsis, &base_w, &h); // continue until we have enough space or only one character left wxCoord w_c, h_c; size_t len = text.Length(); drawntext = text.Left(len); while (len > 1) { dc.GetTextExtent(drawntext.Last(), &w_c, &h_c); drawntext.RemoveLast(); len--; w -= w_c; if (w + base_w <= width) break; } // if still not enough space, remove ellipsis characters while (ellipsis.Length() > 0 && w + base_w > width) { ellipsis = ellipsis.Left(ellipsis.Length() - 1); dc.GetTextExtent(ellipsis, &base_w, &h); } // now draw the text retText = drawntext + ellipsis; } return retText; } /* not virtual in wxGrid, so code copied and modified her for painting sorting icons */ void CBOINCGridCtrl::DrawColLabel( wxDC& dc, int col ) { if ( GetColWidth(col) <= 0 || m_colLabelHeight <= 0 ) return; int colLeft = GetColLeft(col); wxRect rect; int colRight = GetColRight(col) - 1; dc.SetPen( wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW),1, wxSOLID) ); dc.DrawLine( colRight, 0, colRight, m_colLabelHeight-1 ); dc.DrawLine( colLeft, 0, colRight, 0 ); dc.DrawLine( colLeft, m_colLabelHeight-1, colRight+1, m_colLabelHeight-1 ); dc.SetPen( *wxWHITE_PEN ); dc.DrawLine( colLeft, 1, colLeft, m_colLabelHeight-1 ); dc.DrawLine( colLeft, 1, colRight, 1 ); dc.SetBackgroundMode( wxTRANSPARENT ); dc.SetTextForeground( GetLabelTextColour() ); dc.SetFont( GetLabelFont() ); int hAlign, vAlign, orient; GetColLabelAlignment( &hAlign, &vAlign ); orient = GetColLabelTextOrientation(); rect.SetX( colLeft + 2 ); rect.SetY( 2 ); rect.SetWidth( GetColWidth(col) - 4 ); rect.SetHeight( m_colLabelHeight - 4 ); DrawTextRectangle( dc, GetColLabelValue( col ), rect, hAlign, vAlign, orient ); //paint sorting indicators, if needed if(col == sortColumn) { int x = rect.GetRight() - ascBitmap.GetWidth() - 2; int y = rect.GetY(); dc.DrawBitmap(this->sortAscending ? descBitmap : ascBitmap,x,y,true); } } /* handles left mouse click on column header */ void CBOINCGridCtrl::OnLabelLClick(wxGridEvent& ev) { if(ev.GetCol() != -1) { //same column as last time, then change only sort direction if(sortColumn == ev.GetCol()) { sortAscending = ! sortAscending; } else { int tmpOldColumn = sortColumn; sortColumn = ev.GetCol(); sortAscending = true; // Force a repaint of the label if ( -1 != tmpOldColumn ) { SetColLabelValue(tmpOldColumn, GetColLabelValue(tmpOldColumn)); } } // Force a repaint of the label SetColLabelValue(ev.GetCol(), GetColLabelValue(ev.GetCol())); // sortNeededByLabelClick=true; // Update and sort data wxTimerEvent tEvent; wxDynamicCast(GetParent(),CBOINCBaseView)->FireOnListRender(tEvent); } // The base class calls ClearSelection(), so do NOT call ev.Skip(); } void CBOINCGridCtrl::SortData() { GetTable()->SortData(sortColumn,sortAscending); sortNeededByLabelClick=false; } void CBOINCGridCtrl::SetColumnSortType(int col,int sortType/*=CST_STRING*/) { GetTable()->SetColumnSortType(col,sortType); } /* ################### generic grid cell renderer #################### */ CBOINCGridCellRenderer::CBOINCGridCellRenderer() : wxGridCellStringRenderer() { } void CBOINCGridCellRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) { DoNormalTextDrawing(grid,attr,dc,rect,row,col,isSelected); } /* draws alternating background colours */ void CBOINCGridCellRenderer::DrawBackground(wxGrid& grid, wxDC& dc, const wxRect& rect, int row, bool isSelected) { dc.SetBackgroundMode( wxSOLID ); // grey out fields if the grid is disabled if( grid.IsEnabled() ) { if ( isSelected ) { dc.SetBrush( wxBrush(grid.GetSelectionBackground(), wxSOLID) ); } else { //alternate background colours if(row % 2 == 0) { dc.SetBrush(wxBrush(wxColour(240,240,240))); } else { dc.SetBrush(*wxWHITE_BRUSH); } } } else { dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE), wxSOLID)); } dc.SetPen( *wxTRANSPARENT_PEN ); dc.DrawRectangle(rect); } void CBOINCGridCellRenderer::DoNormalTextDrawing(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rectCell, int row, int col, bool isSelected) { wxRect rect = rectCell; rect.Inflate(-1); // erase only this cells background, overflow cells should have been erased this->DrawBackground(grid, dc, rectCell, row, isSelected); int hAlign, vAlign; attr.GetAlignment(&hAlign, &vAlign); int overflowCols = 0; if (attr.GetOverflow()) { int cols = grid.GetNumberCols(); int best_width = GetBestSize(grid,attr,dc,row,col).GetWidth(); int cell_rows, cell_cols; attr.GetSize( &cell_rows, &cell_cols ); // shouldn't get here if <=0 if ((best_width > rectCell.width) && (col < cols) && grid.GetTable()) { int i, c_cols, c_rows; for (i = col+cell_cols; i < cols; i++) { bool is_empty = true; for (int j=row; j 0) c_rows = 0; if (!grid.GetTable()->IsEmptyCell(j+c_rows, i)) { is_empty = false; break; } } if (is_empty) rect.width += grid.GetColSize(i); else { i--; break; } if (rect.width >= best_width) break; } overflowCols = i - col - cell_cols + 1; if (overflowCols >= cols) overflowCols = cols - 1; } if (overflowCols > 0) // redraw overflow cells w/ proper hilight { hAlign = wxALIGN_LEFT; // if oveflowed then it's left aligned wxRect clip = rect; clip.x += rectCell.width; // draw each overflow cell individually int col_end = col+cell_cols+overflowCols; if (col_end >= grid.GetNumberCols()) col_end = grid.GetNumberCols() - 1; for (int i = col+cell_cols; i <= col_end; i++) { clip.width = grid.GetColSize(i) - 1; dc.DestroyClippingRegion(); dc.SetClippingRegion(clip); SetTextColoursAndFont(grid, attr, dc, grid.IsInSelection(row,i)); grid.DrawTextRectangle(dc, grid.GetCellValue(row, col), rect, hAlign, vAlign); clip.x += grid.GetColSize(i) - 1; } rect = rectCell; rect.Inflate(-1); rect.width++; dc.DestroyClippingRegion(); } } // now we only have to draw the text SetTextColoursAndFont(grid, attr, dc, isSelected); //get a real grid class pointer CBOINCGridCtrl* bgrid = wxDynamicCast(&grid, CBOINCGridCtrl); //use the overloaded method here bgrid->DrawTextRectangle(dc, grid.GetCellValue(row, col), rect, hAlign, vAlign); } /* ############# message cell renderer ######################## */ /* Constructor prio argument - the column index, that holds prio values */ CBOINCGridCellMessageRenderer::CBOINCGridCellMessageRenderer(int priocol){ column = priocol; } void CBOINCGridCellMessageRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) { wxString szError(wxT("Error")); if(grid.GetCellValue(row,column).Trim(false).IsSameAs(szError)) { attr.SetTextColour(*wxRED); } else { attr.SetTextColour(*wxBLACK); } CBOINCGridCellRenderer::Draw(grid,attr,dc,rect,row,col,isSelected); } /* ############# progress cell renderer ########################## */ CBOINCGridCellProgressRenderer::CBOINCGridCellProgressRenderer(int col,bool percentAppending/*=true*/) : CBOINCGridCellRenderer() { column = col; m_bDoPercentAppending = percentAppending; } void CBOINCGridCellProgressRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) { if(col==column) { DoProgressDrawing(grid,attr,dc,rect,row,col,isSelected); } else { DoNormalTextDrawing(grid,attr,dc,rect,row,col,isSelected); } } /* paints the progress bar */ void CBOINCGridCellProgressRenderer::DoProgressDrawing(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rectCell, int row, int col, bool isSelected) { wxRect rect = rectCell; rect.Inflate(-1); // erase only this cells background, overflow cells should have been erased this->DrawBackground(grid, dc, rectCell, row, isSelected); // set text attributes int hAlign, vAlign; attr.GetAlignment(&hAlign, &vAlign); SetTextColoursAndFont(grid, attr, dc, isSelected); //calculate the two parts of the progress rect // double dv = 0.0; wxString strValue = grid.GetCellValue(row,col); if(m_bDoPercentAppending) { strValue = strValue + wxT(" %"); } // Project view uses the format: %0.0f (%0.2f%%) // Everyone else uses: %.3f%% if (strValue.Find(wxT("(")) != wxNOT_FOUND) { strValue.SubString(strValue.Find(wxT("(")) + 1, strValue.Find(wxT(")")) - 1).ToDouble( &dv ); } else { strValue.ToDouble ( &dv ); // NOTE: we should do error-checking/reporting here!! } wxRect p1(rect); wxRect p2(rect); int r = (int)((rect.GetRight()-rect.GetLeft())*dv / 100.0); p1.SetRight(rect.GetLeft()+r); p2.SetLeft(rect.GetLeft()+r+1); p2.SetRight(rect.GetRight()-1); //start drawing dc.SetClippingRegion(rect); wxBrush old = dc.GetBrush(); wxColour progressColour = wxTheColourDatabase->Find(wxT("LIGHT BLUE")); wxBrush* progressBrush = wxTheBrushList->FindOrCreateBrush(progressColour); wxPen* progressPen = wxThePenList->FindOrCreatePen(progressColour,1,wxSOLID); //draw the outline rectangle dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(*progressPen); dc.DrawRectangle(rect); // Draw the left part dc.SetBrush(*progressBrush); dc.DrawRectangle(p1); //draw the right part dc.SetBrush(old); dc.DrawRectangle(p2); // dc.DestroyClippingRegion(); // draw the text grid.DrawTextRectangle(dc, strValue, rect, hAlign, vAlign); } /* enables or disbale the appendign of the percent sign to the progress text */ void CBOINCGridCellProgressRenderer::SetPercentAppending(bool enable/*=true*/) { m_bDoPercentAppending = enable; } /* ############ GridTable class ############ */ CBOINCGridTable::CBOINCGridTable(int rows, int cols) : wxGridStringTable(rows,cols) { //init column sort types with CST_STRING for(int i=0; i< cols; i++) { arrColumnSortTypes.Add(CST_STRING); } } CBOINCGridTable::~CBOINCGridTable() { } void CBOINCGridTable::SetColumnSortType(int col,int sortType/*=CST_STRING*/) { if(col>=0 && col < ((int)arrColumnSortTypes.GetCount())) { arrColumnSortTypes[col] = sortType; } } void CBOINCGridTable::SortData(int col,bool ascending) { //valid column index ? if(col<0) { return; } wxArrayString arColValues; //get the values of the sorting column for(int i=0; i < this->GetNumberRows(); i++) { arColValues.Add(this->GetValue(i,col)); } //sort the sorting column values reverseCompareOrder = !ascending; //decide, which sort function to call switch(arrColumnSortTypes[col]) { case CST_FLOAT: arColValues.Sort(CompareFloatString); break; case CST_LONG: arColValues.Sort(CompareLongString); break; case CST_TIME: arColValues.Sort(CompareTimeString); break; case CST_DATETIME: arColValues.Sort(CompareDateString); break; default: arColValues.Sort(CompareStringStringNoCase); break; } //rebuild the underlaying 2-dimensional string array wxGridStringArray newArray; for(unsigned int i=0; i< arColValues.GetCount();i++) { //find the original row index int indexold = FindRowIndexByColValue(col,arColValues[i],-1,arColValues[i]); wxArrayString rowArray; for(int j=0; j < this->GetNumberCols(); j++) { rowArray.Add(this->GetValue(indexold,j)); //delete the value in the original string array //to prevent finding the wrong index in case of identical values //f.e. the project name is the same for multiple work units or transfers this->SetValue(indexold,j,wxEmptyString); } newArray.Add(rowArray); } //fill the table data array with the sorted values for(unsigned int i=0; i< newArray.GetCount(); i++) { for(unsigned int j=0; j < newArray[i].GetCount();j++) { this->SetValue(i,j,newArray[i][j]); } } } /* finds the first row index for the cell with value value1 in column col1 and value2 in col2 ignores col2 and value2 if col2 < 0 !! only use this (outside sorting method) with a pair of columns, that have unique value pairs !! */ int CBOINCGridTable::FindRowIndexByColValue(int col1,wxString& value1,int col2,wxString& value2) { for(int i=0; i < this->GetNumberRows(); i++) { if(! this->GetValue(i,col1).IsSameAs(value1)) continue; if (col2 < 0) return i; if(this->GetValue(i,col2).IsSameAs(value2)) return i; } return -1; } /* for convinience purposes only */ CBOINCGridTable* CBOINCGridCtrl::GetTable() { return wxDynamicCast(wxGrid::GetTable(), CBOINCGridTable); }